From 29471d18af97c4e0f53d62047e5229707c754b78 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 18 Dec 2025 10:39:54 +0530 Subject: [PATCH 1/6] added channel helpers for realtime in the client sdks --- src/SDK/Language/Android.php | 5 + src/SDK/Language/Apple.php | 5 + src/SDK/Language/Dart.php | 5 + src/SDK/Language/Flutter.php | 10 ++ src/SDK/Language/ReactNative.php | 5 + src/SDK/Language/Web.php | 5 + .../src/main/java/io/package/Channel.kt.twig | 116 ++++++++++++++++++ templates/dart/lib/channel.dart.twig | 116 ++++++++++++++++++ templates/dart/lib/package.dart.twig | 1 + templates/dart/test/channel_test.dart.twig | 112 +++++++++++++++++ templates/react-native/src/channel.ts.twig | 111 +++++++++++++++++ templates/react-native/src/index.ts.twig | 1 + templates/swift/Sources/Channel.swift.twig | 108 ++++++++++++++++ templates/web/src/channel.ts.twig | 111 +++++++++++++++++ templates/web/src/index.ts.twig | 1 + tests/languages/android/Tests.kt | 22 ++++ tests/languages/apple/Tests.swift | 22 ++++ tests/languages/flutter/tests.dart | 22 ++++ tests/languages/node/test.js | 22 ++++ tests/languages/swift/Tests.swift | 22 ++++ tests/languages/web/index.html | 22 ++++ tests/languages/web/node.js | 22 ++++ 22 files changed, 866 insertions(+) create mode 100644 templates/android/library/src/main/java/io/package/Channel.kt.twig create mode 100644 templates/dart/lib/channel.dart.twig create mode 100644 templates/dart/test/channel_test.dart.twig create mode 100644 templates/react-native/src/channel.ts.twig create mode 100644 templates/swift/Sources/Channel.swift.twig create mode 100644 templates/web/src/channel.ts.twig diff --git a/src/SDK/Language/Android.php b/src/SDK/Language/Android.php index 19a22f01a..64242bd12 100644 --- a/src/SDK/Language/Android.php +++ b/src/SDK/Language/Android.php @@ -120,6 +120,11 @@ public function getFiles(): array 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/ID.kt', 'template' => '/android/library/src/main/java/io/package/ID.kt.twig', ], + [ + 'scope' => 'default', + 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/Channel.kt', + 'template' => '/android/library/src/main/java/io/package/Channel.kt.twig', + ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/Query.kt', diff --git a/src/SDK/Language/Apple.php b/src/SDK/Language/Apple.php index 4313f9d34..3f409b593 100644 --- a/src/SDK/Language/Apple.php +++ b/src/SDK/Language/Apple.php @@ -70,6 +70,11 @@ public function getFiles(): array 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/ID.swift', 'template' => 'swift/Sources/ID.swift.twig', ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Channel.swift', + 'template' => 'swift/Sources/Channel.swift.twig', + ], [ 'scope' => 'default', 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Query.swift', diff --git a/src/SDK/Language/Dart.php b/src/SDK/Language/Dart.php index 6ad8efbaa..01e197509 100644 --- a/src/SDK/Language/Dart.php +++ b/src/SDK/Language/Dart.php @@ -464,6 +464,11 @@ public function getFiles(): array 'destination' => '/test/role_test.dart', 'template' => 'dart/test/role_test.dart.twig', ], + [ + 'scope' => 'default', + 'destination' => '/test/channel_test.dart', + 'template' => 'dart/test/channel_test.dart.twig', + ], [ 'scope' => 'default', 'destination' => '/test/src/enums_test.dart', diff --git a/src/SDK/Language/Flutter.php b/src/SDK/Language/Flutter.php index ab0c5ec3a..613720c09 100644 --- a/src/SDK/Language/Flutter.php +++ b/src/SDK/Language/Flutter.php @@ -80,6 +80,11 @@ public function getFiles(): array 'destination' => '/lib/id.dart', 'template' => 'dart/lib/id.dart.twig', ], + [ + 'scope' => 'default', + 'destination' => '/lib/channel.dart', + 'template' => 'dart/lib/channel.dart.twig', + ], [ 'scope' => 'default', 'destination' => '/lib/query.dart', @@ -290,6 +295,11 @@ public function getFiles(): array 'destination' => '/test/role_test.dart', 'template' => 'dart/test/role_test.dart.twig', ], + [ + 'scope' => 'default', + 'destination' => '/test/channel_test.dart', + 'template' => 'dart/test/channel_test.dart.twig', + ], [ 'scope' => 'default', 'destination' => '/test/src/cookie_manager_test.dart', diff --git a/src/SDK/Language/ReactNative.php b/src/SDK/Language/ReactNative.php index 81748709c..f0ccd54c2 100644 --- a/src/SDK/Language/ReactNative.php +++ b/src/SDK/Language/ReactNative.php @@ -60,6 +60,11 @@ public function getFiles(): array 'destination' => 'src/id.ts', 'template' => 'react-native/src/id.ts.twig', ], + [ + 'scope' => 'default', + 'destination' => 'src/channel.ts', + 'template' => 'react-native/src/channel.ts.twig', + ], [ 'scope' => 'default', 'destination' => 'src/query.ts', diff --git a/src/SDK/Language/Web.php b/src/SDK/Language/Web.php index 2490f833f..4a94da3a9 100644 --- a/src/SDK/Language/Web.php +++ b/src/SDK/Language/Web.php @@ -80,6 +80,11 @@ public function getFiles(): array 'destination' => 'src/id.ts', 'template' => 'web/src/id.ts.twig', ], + [ + 'scope' => 'default', + 'destination' => 'src/channel.ts', + 'template' => 'web/src/channel.ts.twig', + ], [ 'scope' => 'default', 'destination' => 'src/query.ts', diff --git a/templates/android/library/src/main/java/io/package/Channel.kt.twig b/templates/android/library/src/main/java/io/package/Channel.kt.twig new file mode 100644 index 000000000..10f9fcd41 --- /dev/null +++ b/templates/android/library/src/main/java/io/package/Channel.kt.twig @@ -0,0 +1,116 @@ +package {{ sdk.namespace | caseDot }} + +/** + * Helper class to generate channel strings for realtime subscriptions. + */ +class Channel { + companion object { + + /** + * Generate a database channel string. + * + * @param databaseId The database ID (default: "*") + * @param collectionId The collection ID (default: "*") + * @param documentId The document ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: null) + * @returns The channel string + */ + fun database(databaseId: String = "*", collectionId: String = "*", documentId: String = "*", action: String? = null): String { + var channel = "databases.$databaseId.collections.$collectionId.documents.$documentId" + if (action != null) { + channel += ".$action" + } + return channel + } + + /** + * Generate a tables database channel string. + * + * @param databaseId The database ID (default: "*") + * @param tableId The table ID (default: "*") + * @param rowId The row ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: null) + * @returns The channel string + */ + fun tablesdb(databaseId: String = "*", tableId: String = "*", rowId: String = "*", action: String? = null): String { + var channel = "tablesdb.$databaseId.tables.$tableId.rows.$rowId" + if (action != null) { + channel += ".$action" + } + return channel + } + + /** + * Generate an account channel string. + * + * @param userId The user ID (default: "*") + * @returns The channel string + */ + fun account(userId: String = "*"): String { + return "account.$userId" + } + + /** + * Generate a files channel string. + * + * @param bucketId The bucket ID (default: "*") + * @param fileId The file ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: null) + * @returns The channel string + */ + fun files(bucketId: String = "*", fileId: String = "*", action: String? = null): String { + var channel = "buckets.$bucketId.files.$fileId" + if (action != null) { + channel += ".$action" + } + return channel + } + + /** + * Generate an executions channel string. + * + * @param functionId The function ID (default: "*") + * @param executionId The execution ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: null) + * @returns The channel string + */ + fun executions(functionId: String = "*", executionId: String = "*", action: String? = null): String { + var channel = "functions.$functionId.executions.$executionId" + if (action != null) { + channel += ".$action" + } + return channel + } + + /** + * Generate a teams channel string. + * + * @param teamId The team ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: null) + * @returns The channel string + */ + fun teams(teamId: String = "*", action: String? = null): String { + var channel = "teams.$teamId" + if (action != null) { + channel += ".$action" + } + return channel + } + + /** + * Generate a memberships channel string. + * + * @param membershipId The membership ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: null) + * @returns The channel string + */ + fun memberships(membershipId: String = "*", action: String? = null): String { + var channel = "memberships.$membershipId" + if (action != null) { + channel += ".$action" + } + return channel + } + } +} + diff --git a/templates/dart/lib/channel.dart.twig b/templates/dart/lib/channel.dart.twig new file mode 100644 index 000000000..7966dfacb --- /dev/null +++ b/templates/dart/lib/channel.dart.twig @@ -0,0 +1,116 @@ +part of '{{ language.params.packageName }}.dart'; + +/// Helper class to generate channel strings for realtime subscriptions. +class Channel { + Channel._(); + + /// Generate a database channel string. + /// + /// [databaseId] The database ID (default: '*') + /// [collectionId] The collection ID (default: '*') + /// [documentId] The document ID (default: '*') + /// [action] Optional action: 'create', 'update', or 'delete' (default: null) + static String database({ + String databaseId = '*', + String collectionId = '*', + String documentId = '*', + String? action, + }) { + String channel = 'databases.$databaseId.collections.$collectionId.documents.$documentId'; + if (action != null) { + channel += '.$action'; + } + return channel; + } + + /// Generate a tables database channel string. + /// + /// [databaseId] The database ID (default: '*') + /// [tableId] The table ID (default: '*') + /// [rowId] The row ID (default: '*') + /// [action] Optional action: 'create', 'update', or 'delete' (default: null) + static String tablesdb({ + String databaseId = '*', + String tableId = '*', + String rowId = '*', + String? action, + }) { + String channel = 'tablesdb.$databaseId.tables.$tableId.rows.$rowId'; + if (action != null) { + channel += '.$action'; + } + return channel; + } + + /// Generate an account channel string. + /// + /// [userId] The user ID (default: '*') + static String account({String userId = '*'}) { + return 'account.$userId'; + } + + /// Generate a files channel string. + /// + /// [bucketId] The bucket ID (default: '*') + /// [fileId] The file ID (default: '*') + /// [action] Optional action: 'create', 'update', or 'delete' (default: null) + static String files({ + String bucketId = '*', + String fileId = '*', + String? action, + }) { + String channel = 'buckets.$bucketId.files.$fileId'; + if (action != null) { + channel += '.$action'; + } + return channel; + } + + /// Generate an executions channel string. + /// + /// [functionId] The function ID (default: '*') + /// [executionId] The execution ID (default: '*') + /// [action] Optional action: 'create', 'update', or 'delete' (default: null) + static String executions({ + String functionId = '*', + String executionId = '*', + String? action, + }) { + String channel = 'functions.$functionId.executions.$executionId'; + if (action != null) { + channel += '.$action'; + } + return channel; + } + + /// Generate a teams channel string. + /// + /// [teamId] The team ID (default: '*') + /// [action] Optional action: 'create', 'update', or 'delete' (default: null) + static String teams({ + String teamId = '*', + String? action, + }) { + String channel = 'teams.$teamId'; + if (action != null) { + channel += '.$action'; + } + return channel; + } + + /// Generate a memberships channel string. + /// + /// [membershipId] The membership ID (default: '*') + /// [action] Optional action: 'create', 'update', or 'delete' (default: null) + static String memberships({ + String membershipId = '*', + String? action, + }) { + String channel = 'memberships.$membershipId'; + if (action != null) { + channel += '.$action'; + } + return channel; + } +} + diff --git a/templates/dart/lib/package.dart.twig b/templates/dart/lib/package.dart.twig index bca6862a3..43e4a2c76 100644 --- a/templates/dart/lib/package.dart.twig +++ b/templates/dart/lib/package.dart.twig @@ -27,6 +27,7 @@ part 'query.dart'; part 'permission.dart'; part 'role.dart'; part 'id.dart'; +part 'channel.dart'; part 'operator.dart'; {% for service in spec.services %} part 'services/{{service.name | caseSnake}}.dart'; diff --git a/templates/dart/test/channel_test.dart.twig b/templates/dart/test/channel_test.dart.twig new file mode 100644 index 000000000..2a3dd5953 --- /dev/null +++ b/templates/dart/test/channel_test.dart.twig @@ -0,0 +1,112 @@ +import 'package:{{ language.params.packageName }}/{{ language.params.packageName }}.dart'; +{% if 'dart' in language.params.packageName %} +import 'package:test/test.dart'; +{% else %} +import 'package:flutter_test/flutter_test.dart'; +{% endif %} + +void main() { + group('database()', () { + test('returns database channel with defaults', () { + expect(Channel.database(), 'databases.*.collections.*.documents.*'); + }); + + test('returns database channel with specific IDs', () { + expect(Channel.database(databaseId: 'db1', collectionId: 'col1', documentId: 'doc1'), + 'databases.db1.collections.col1.documents.doc1'); + }); + + test('returns database channel with action', () { + expect(Channel.database(databaseId: 'db1', collectionId: 'col1', documentId: 'doc1', action: 'create'), + 'databases.db1.collections.col1.documents.doc1.create'); + }); + }); + + group('tablesdb()', () { + test('returns tablesdb channel with defaults', () { + expect(Channel.tablesdb(), 'tablesdb.*.tables.*.rows.*'); + }); + + test('returns tablesdb channel with specific IDs', () { + expect(Channel.tablesdb(databaseId: 'db1', tableId: 'table1', rowId: 'row1'), + 'tablesdb.db1.tables.table1.rows.row1'); + }); + + test('returns tablesdb channel with action', () { + expect(Channel.tablesdb(databaseId: 'db1', tableId: 'table1', rowId: 'row1', action: 'update'), + 'tablesdb.db1.tables.table1.rows.row1.update'); + }); + }); + + group('account()', () { + test('returns account channel with default', () { + expect(Channel.account(), 'account.*'); + }); + + test('returns account channel with specific user ID', () { + expect(Channel.account(userId: 'user123'), 'account.user123'); + }); + }); + + group('files()', () { + test('returns files channel with defaults', () { + expect(Channel.files(), 'buckets.*.files.*'); + }); + + test('returns files channel with specific IDs', () { + expect(Channel.files(bucketId: 'bucket1', fileId: 'file1'), + 'buckets.bucket1.files.file1'); + }); + + test('returns files channel with action', () { + expect(Channel.files(bucketId: 'bucket1', fileId: 'file1', action: 'delete'), + 'buckets.bucket1.files.file1.delete'); + }); + }); + + group('executions()', () { + test('returns executions channel with defaults', () { + expect(Channel.executions(), 'functions.*.executions.*'); + }); + + test('returns executions channel with specific IDs', () { + expect(Channel.executions(functionId: 'func1', executionId: 'exec1'), + 'functions.func1.executions.exec1'); + }); + + test('returns executions channel with action', () { + expect(Channel.executions(functionId: 'func1', executionId: 'exec1', action: 'create'), + 'functions.func1.executions.exec1.create'); + }); + }); + + group('teams()', () { + test('returns teams channel with default', () { + expect(Channel.teams(), 'teams.*'); + }); + + test('returns teams channel with specific team ID', () { + expect(Channel.teams(teamId: 'team1'), 'teams.team1'); + }); + + test('returns teams channel with action', () { + expect(Channel.teams(teamId: 'team1', action: 'create'), 'teams.team1.create'); + }); + }); + + group('memberships()', () { + test('returns memberships channel with default', () { + expect(Channel.memberships(), 'memberships.*'); + }); + + test('returns memberships channel with specific membership ID', () { + expect(Channel.memberships(membershipId: 'membership1'), 'memberships.membership1'); + }); + + test('returns memberships channel with action', () { + expect(Channel.memberships(membershipId: 'membership1', action: 'update'), + 'memberships.membership1.update'); + }); + }); +} + diff --git a/templates/react-native/src/channel.ts.twig b/templates/react-native/src/channel.ts.twig new file mode 100644 index 000000000..fe2cada9e --- /dev/null +++ b/templates/react-native/src/channel.ts.twig @@ -0,0 +1,111 @@ +/** + * Helper class to generate channel strings for realtime subscriptions. + */ +export class Channel { + /** + * Generate a database channel string. + * + * @param {string} databaseId + * @param {string} collectionId + * @param {string} documentId + * @param {'create'|'update'|'delete'|null} action + * @returns {string} + */ + static database(databaseId: string = '*', collectionId: string = '*', documentId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { + let channel = `databases.${databaseId}.collections.${collectionId}.documents.${documentId}`; + if (action) { + channel += `.${action}`; + } + return channel; + } + + /** + * Generate a tables database channel string. + * + * @param {string} databaseId + * @param {string} tableId + * @param {string} rowId + * @param {'create'|'update'|'delete'|null} action + * @returns {string} + */ + static tablesdb(databaseId: string = '*', tableId: string = '*', rowId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { + let channel = `tablesdb.${databaseId}.tables.${tableId}.rows.${rowId}`; + if (action) { + channel += `.${action}`; + } + return channel; + } + + /** + * Generate an account channel string. + * + * @param {string} userId + * @returns {string} + */ + static account(userId: string = '*'): string { + return `account.${userId}`; + } + + /** + * Generate a files channel string. + * + * @param {string} bucketId + * @param {string} fileId + * @param {'create'|'update'|'delete'|null} action + * @returns {string} + */ + static files(bucketId: string = '*', fileId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { + let channel = `buckets.${bucketId}.files.${fileId}`; + if (action) { + channel += `.${action}`; + } + return channel; + } + + /** + * Generate an executions channel string. + * + * @param {string} functionId + * @param {string} executionId + * @param {'create'|'update'|'delete'|null} action + * @returns {string} + */ + static executions(functionId: string = '*', executionId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { + let channel = `functions.${functionId}.executions.${executionId}`; + if (action) { + channel += `.${action}`; + } + return channel; + } + + /** + * Generate a teams channel string. + * + * @param {string} teamId + * @param {'create'|'update'|'delete'|null} action + * @returns {string} + */ + static teams(teamId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { + let channel = `teams.${teamId}`; + if (action) { + channel += `.${action}`; + } + return channel; + } + + /** + * Generate a memberships channel string. + * + * @param {string} membershipId + * @param {'create'|'update'|'delete'|null} action + * @returns {string} + */ + static memberships(membershipId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { + let channel = `memberships.${membershipId}`; + if (action) { + channel += `.${action}`; + } + return channel; + } +} + diff --git a/templates/react-native/src/index.ts.twig b/templates/react-native/src/index.ts.twig index 8a9b5aa94..e27f29649 100644 --- a/templates/react-native/src/index.ts.twig +++ b/templates/react-native/src/index.ts.twig @@ -8,6 +8,7 @@ export { Query } from './query'; export { Permission } from './permission'; export { Role } from './role'; export { ID } from './id'; +export { Channel } from './channel'; export { Operator, Condition } from './operator'; {% for enum in spec.allEnums %} export { {{ enum.name | caseUcfirst }} } from './enums/{{enum.name | caseKebab}}'; diff --git a/templates/swift/Sources/Channel.swift.twig b/templates/swift/Sources/Channel.swift.twig new file mode 100644 index 000000000..ba14f2f20 --- /dev/null +++ b/templates/swift/Sources/Channel.swift.twig @@ -0,0 +1,108 @@ +public class Channel { + /** + * Generate a database channel string. + * + * @param databaseId The database ID (default: "*") + * @param collectionId The collection ID (default: "*") + * @param documentId The document ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: nil) + * @returns The channel string + */ + public static func database(databaseId: String = "*", collectionId: String = "*", documentId: String = "*", action: String? = nil) -> String { + var channel = "databases.\(databaseId).collections.\(collectionId).documents.\(documentId)" + if let action = action { + channel += ".\(action)" + } + return channel + } + + /** + * Generate a tables database channel string. + * + * @param databaseId The database ID (default: "*") + * @param tableId The table ID (default: "*") + * @param rowId The row ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: nil) + * @returns The channel string + */ + public static func tablesdb(databaseId: String = "*", tableId: String = "*", rowId: String = "*", action: String? = nil) -> String { + var channel = "tablesdb.\(databaseId).tables.\(tableId).rows.\(rowId)" + if let action = action { + channel += ".\(action)" + } + return channel + } + + /** + * Generate an account channel string. + * + * @param userId The user ID (default: "*") + * @returns The channel string + */ + public static func account(userId: String = "*") -> String { + return "account.\(userId)" + } + + /** + * Generate a files channel string. + * + * @param bucketId The bucket ID (default: "*") + * @param fileId The file ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: nil) + * @returns The channel string + */ + public static func files(bucketId: String = "*", fileId: String = "*", action: String? = nil) -> String { + var channel = "buckets.\(bucketId).files.\(fileId)" + if let action = action { + channel += ".\(action)" + } + return channel + } + + /** + * Generate an executions channel string. + * + * @param functionId The function ID (default: "*") + * @param executionId The execution ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: nil) + * @returns The channel string + */ + public static func executions(functionId: String = "*", executionId: String = "*", action: String? = nil) -> String { + var channel = "functions.\(functionId).executions.\(executionId)" + if let action = action { + channel += ".\(action)" + } + return channel + } + + /** + * Generate a teams channel string. + * + * @param teamId The team ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: nil) + * @returns The channel string + */ + public static func teams(teamId: String = "*", action: String? = nil) -> String { + var channel = "teams.\(teamId)" + if let action = action { + channel += ".\(action)" + } + return channel + } + + /** + * Generate a memberships channel string. + * + * @param membershipId The membership ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: nil) + * @returns The channel string + */ + public static func memberships(membershipId: String = "*", action: String? = nil) -> String { + var channel = "memberships.\(membershipId)" + if let action = action { + channel += ".\(action)" + } + return channel + } +} + diff --git a/templates/web/src/channel.ts.twig b/templates/web/src/channel.ts.twig new file mode 100644 index 000000000..fe2cada9e --- /dev/null +++ b/templates/web/src/channel.ts.twig @@ -0,0 +1,111 @@ +/** + * Helper class to generate channel strings for realtime subscriptions. + */ +export class Channel { + /** + * Generate a database channel string. + * + * @param {string} databaseId + * @param {string} collectionId + * @param {string} documentId + * @param {'create'|'update'|'delete'|null} action + * @returns {string} + */ + static database(databaseId: string = '*', collectionId: string = '*', documentId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { + let channel = `databases.${databaseId}.collections.${collectionId}.documents.${documentId}`; + if (action) { + channel += `.${action}`; + } + return channel; + } + + /** + * Generate a tables database channel string. + * + * @param {string} databaseId + * @param {string} tableId + * @param {string} rowId + * @param {'create'|'update'|'delete'|null} action + * @returns {string} + */ + static tablesdb(databaseId: string = '*', tableId: string = '*', rowId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { + let channel = `tablesdb.${databaseId}.tables.${tableId}.rows.${rowId}`; + if (action) { + channel += `.${action}`; + } + return channel; + } + + /** + * Generate an account channel string. + * + * @param {string} userId + * @returns {string} + */ + static account(userId: string = '*'): string { + return `account.${userId}`; + } + + /** + * Generate a files channel string. + * + * @param {string} bucketId + * @param {string} fileId + * @param {'create'|'update'|'delete'|null} action + * @returns {string} + */ + static files(bucketId: string = '*', fileId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { + let channel = `buckets.${bucketId}.files.${fileId}`; + if (action) { + channel += `.${action}`; + } + return channel; + } + + /** + * Generate an executions channel string. + * + * @param {string} functionId + * @param {string} executionId + * @param {'create'|'update'|'delete'|null} action + * @returns {string} + */ + static executions(functionId: string = '*', executionId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { + let channel = `functions.${functionId}.executions.${executionId}`; + if (action) { + channel += `.${action}`; + } + return channel; + } + + /** + * Generate a teams channel string. + * + * @param {string} teamId + * @param {'create'|'update'|'delete'|null} action + * @returns {string} + */ + static teams(teamId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { + let channel = `teams.${teamId}`; + if (action) { + channel += `.${action}`; + } + return channel; + } + + /** + * Generate a memberships channel string. + * + * @param {string} membershipId + * @param {'create'|'update'|'delete'|null} action + * @returns {string} + */ + static memberships(membershipId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { + let channel = `memberships.${membershipId}`; + if (action) { + channel += `.${action}`; + } + return channel; + } +} + diff --git a/templates/web/src/index.ts.twig b/templates/web/src/index.ts.twig index c9aba90fc..54ad22739 100644 --- a/templates/web/src/index.ts.twig +++ b/templates/web/src/index.ts.twig @@ -15,6 +15,7 @@ export type { QueryTypes, QueryTypesList } from './query'; export { Permission } from './permission'; export { Role } from './role'; export { ID } from './id'; +export { Channel } from './channel'; export { Operator, Condition } from './operator'; {% for enum in spec.allEnums %} export { {{ enum.name | caseUcfirst }} } from './enums/{{enum.name | caseKebab}}'; diff --git a/tests/languages/android/Tests.kt b/tests/languages/android/Tests.kt index aa8a083ef..eaed5dd53 100644 --- a/tests/languages/android/Tests.kt +++ b/tests/languages/android/Tests.kt @@ -269,6 +269,28 @@ class ServiceTest { writeToFile(ID.unique()) writeToFile(ID.custom("custom_id")) + // Channel helper tests + writeToFile(Channel.database()) + writeToFile(Channel.database("db1", "col1", "doc1")) + writeToFile(Channel.database("db1", "col1", "doc1", "create")) + writeToFile(Channel.tablesdb()) + writeToFile(Channel.tablesdb("db1", "table1", "row1")) + writeToFile(Channel.tablesdb("db1", "table1", "row1", "update")) + writeToFile(Channel.account()) + writeToFile(Channel.account("user123")) + writeToFile(Channel.files()) + writeToFile(Channel.files("bucket1", "file1")) + writeToFile(Channel.files("bucket1", "file1", "delete")) + writeToFile(Channel.executions()) + writeToFile(Channel.executions("func1", "exec1")) + writeToFile(Channel.executions("func1", "exec1", "create")) + writeToFile(Channel.teams()) + writeToFile(Channel.teams("team1")) + writeToFile(Channel.teams("team1", "create")) + writeToFile(Channel.memberships()) + writeToFile(Channel.memberships("membership1")) + writeToFile(Channel.memberships("membership1", "update")) + // Operator helper tests writeToFile(Operator.increment(1)) writeToFile(Operator.increment(5, 100)) diff --git a/tests/languages/apple/Tests.swift b/tests/languages/apple/Tests.swift index ae8b55b92..8917ce1b2 100644 --- a/tests/languages/apple/Tests.swift +++ b/tests/languages/apple/Tests.swift @@ -248,6 +248,28 @@ class Tests: XCTestCase { print(ID.unique()) print(ID.custom("custom_id")) + // Channel helper tests + print(Channel.database()) + print(Channel.database(databaseId: "db1", collectionId: "col1", documentId: "doc1")) + print(Channel.database(databaseId: "db1", collectionId: "col1", documentId: "doc1", action: "create")) + print(Channel.tablesdb()) + print(Channel.tablesdb(databaseId: "db1", tableId: "table1", rowId: "row1")) + print(Channel.tablesdb(databaseId: "db1", tableId: "table1", rowId: "row1", action: "update")) + print(Channel.account()) + print(Channel.account(userId: "user123")) + print(Channel.files()) + print(Channel.files(bucketId: "bucket1", fileId: "file1")) + print(Channel.files(bucketId: "bucket1", fileId: "file1", action: "delete")) + print(Channel.executions()) + print(Channel.executions(functionId: "func1", executionId: "exec1")) + print(Channel.executions(functionId: "func1", executionId: "exec1", action: "create")) + print(Channel.teams()) + print(Channel.teams(teamId: "team1")) + print(Channel.teams(teamId: "team1", action: "create")) + print(Channel.memberships()) + print(Channel.memberships(membershipId: "membership1")) + print(Channel.memberships(membershipId: "membership1", action: "update")) + // Operator helper tests print(Operator.increment(1)) print(Operator.increment(5, max: 100)) diff --git a/tests/languages/flutter/tests.dart b/tests/languages/flutter/tests.dart index 939c0ff14..c6ab56563 100644 --- a/tests/languages/flutter/tests.dart +++ b/tests/languages/flutter/tests.dart @@ -247,6 +247,28 @@ void main() async { print(ID.unique()); print(ID.custom('custom_id')); + // Channel helper tests + print(Channel.database()); + print(Channel.database(databaseId: 'db1', collectionId: 'col1', documentId: 'doc1')); + print(Channel.database(databaseId: 'db1', collectionId: 'col1', documentId: 'doc1', action: 'create')); + print(Channel.tablesdb()); + print(Channel.tablesdb(databaseId: 'db1', tableId: 'table1', rowId: 'row1')); + print(Channel.tablesdb(databaseId: 'db1', tableId: 'table1', rowId: 'row1', action: 'update')); + print(Channel.account()); + print(Channel.account(userId: 'user123')); + print(Channel.files()); + print(Channel.files(bucketId: 'bucket1', fileId: 'file1')); + print(Channel.files(bucketId: 'bucket1', fileId: 'file1', action: 'delete')); + print(Channel.executions()); + print(Channel.executions(functionId: 'func1', executionId: 'exec1')); + print(Channel.executions(functionId: 'func1', executionId: 'exec1', action: 'create')); + print(Channel.teams()); + print(Channel.teams(teamId: 'team1')); + print(Channel.teams(teamId: 'team1', action: 'create')); + print(Channel.memberships()); + print(Channel.memberships(membershipId: 'membership1')); + print(Channel.memberships(membershipId: 'membership1', action: 'update')); + // Operator helper tests print(Operator.increment(1)); print(Operator.increment(5, 100)); diff --git a/tests/languages/node/test.js b/tests/languages/node/test.js index ee0fd703f..c52aafd28 100644 --- a/tests/languages/node/test.js +++ b/tests/languages/node/test.js @@ -326,6 +326,28 @@ async function start() { console.log(ID.unique()); console.log(ID.custom('custom_id')); + // Channel helper tests + console.log(Channel.database()); + console.log(Channel.database('db1', 'col1', 'doc1')); + console.log(Channel.database('db1', 'col1', 'doc1', 'create')); + console.log(Channel.tablesdb()); + console.log(Channel.tablesdb('db1', 'table1', 'row1')); + console.log(Channel.tablesdb('db1', 'table1', 'row1', 'update')); + console.log(Channel.account()); + console.log(Channel.account('user123')); + console.log(Channel.files()); + console.log(Channel.files('bucket1', 'file1')); + console.log(Channel.files('bucket1', 'file1', 'delete')); + console.log(Channel.executions()); + console.log(Channel.executions('func1', 'exec1')); + console.log(Channel.executions('func1', 'exec1', 'create')); + console.log(Channel.teams()); + console.log(Channel.teams('team1')); + console.log(Channel.teams('team1', 'create')); + console.log(Channel.memberships()); + console.log(Channel.memberships('membership1')); + console.log(Channel.memberships('membership1', 'update')); + // Operator helper tests console.log(Operator.increment(1)); console.log(Operator.increment(5, 100)); diff --git a/tests/languages/swift/Tests.swift b/tests/languages/swift/Tests.swift index 8719dc646..d682ff60d 100644 --- a/tests/languages/swift/Tests.swift +++ b/tests/languages/swift/Tests.swift @@ -235,6 +235,28 @@ class Tests: XCTestCase { print(ID.unique()) print(ID.custom("custom_id")) + // Channel helper tests + print(Channel.database()) + print(Channel.database(databaseId: "db1", collectionId: "col1", documentId: "doc1")) + print(Channel.database(databaseId: "db1", collectionId: "col1", documentId: "doc1", action: "create")) + print(Channel.tablesdb()) + print(Channel.tablesdb(databaseId: "db1", tableId: "table1", rowId: "row1")) + print(Channel.tablesdb(databaseId: "db1", tableId: "table1", rowId: "row1", action: "update")) + print(Channel.account()) + print(Channel.account(userId: "user123")) + print(Channel.files()) + print(Channel.files(bucketId: "bucket1", fileId: "file1")) + print(Channel.files(bucketId: "bucket1", fileId: "file1", action: "delete")) + print(Channel.executions()) + print(Channel.executions(functionId: "func1", executionId: "exec1")) + print(Channel.executions(functionId: "func1", executionId: "exec1", action: "create")) + print(Channel.teams()) + print(Channel.teams(teamId: "team1")) + print(Channel.teams(teamId: "team1", action: "create")) + print(Channel.memberships()) + print(Channel.memberships(membershipId: "membership1")) + print(Channel.memberships(membershipId: "membership1", action: "update")) + // Operator helper tests print(Operator.increment(1)) print(Operator.increment(5, max: 100)) diff --git a/tests/languages/web/index.html b/tests/languages/web/index.html index 7e81de9c5..8aced2b34 100644 --- a/tests/languages/web/index.html +++ b/tests/languages/web/index.html @@ -319,6 +319,28 @@ console.log(ID.unique()); console.log(ID.custom('custom_id')); + // Channel helper tests + console.log(Channel.database()); + console.log(Channel.database('db1', 'col1', 'doc1')); + console.log(Channel.database('db1', 'col1', 'doc1', 'create')); + console.log(Channel.tablesdb()); + console.log(Channel.tablesdb('db1', 'table1', 'row1')); + console.log(Channel.tablesdb('db1', 'table1', 'row1', 'update')); + console.log(Channel.account()); + console.log(Channel.account('user123')); + console.log(Channel.files()); + console.log(Channel.files('bucket1', 'file1')); + console.log(Channel.files('bucket1', 'file1', 'delete')); + console.log(Channel.executions()); + console.log(Channel.executions('func1', 'exec1')); + console.log(Channel.executions('func1', 'exec1', 'create')); + console.log(Channel.teams()); + console.log(Channel.teams('team1')); + console.log(Channel.teams('team1', 'create')); + console.log(Channel.memberships()); + console.log(Channel.memberships('membership1')); + console.log(Channel.memberships('membership1', 'update')); + // Operator helper tests console.log(Operator.increment(1)); console.log(Operator.increment(5, 100)); diff --git a/tests/languages/web/node.js b/tests/languages/web/node.js index f4c7bed43..8e82b3478 100644 --- a/tests/languages/web/node.js +++ b/tests/languages/web/node.js @@ -250,6 +250,28 @@ async function start() { console.log(ID.unique()); console.log(ID.custom('custom_id')); + // Channel helper tests + console.log(Channel.database()); + console.log(Channel.database('db1', 'col1', 'doc1')); + console.log(Channel.database('db1', 'col1', 'doc1', 'create')); + console.log(Channel.tablesdb()); + console.log(Channel.tablesdb('db1', 'table1', 'row1')); + console.log(Channel.tablesdb('db1', 'table1', 'row1', 'update')); + console.log(Channel.account()); + console.log(Channel.account('user123')); + console.log(Channel.files()); + console.log(Channel.files('bucket1', 'file1')); + console.log(Channel.files('bucket1', 'file1', 'delete')); + console.log(Channel.executions()); + console.log(Channel.executions('func1', 'exec1')); + console.log(Channel.executions('func1', 'exec1', 'create')); + console.log(Channel.teams()); + console.log(Channel.teams('team1')); + console.log(Channel.teams('team1', 'create')); + console.log(Channel.memberships()); + console.log(Channel.memberships('membership1')); + console.log(Channel.memberships('membership1', 'update')); + // Operator helper tests console.log(Operator.increment(1)); console.log(Operator.increment(5, 100)); From 3f13cb1a000ab5311993be949cdbfccb10b04c16 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 18 Dec 2025 23:24:02 +0530 Subject: [PATCH 2/6] Add Channel support to SDKs and templates - Updated Apple and Dart SDKs to include Channel templates. - Added Channel class in Swift template for Apple SDK. - Included Channel in Dart SDK template and test files. - Updated test cases across various platforms to incorporate Channel responses. - Modified web and node.js files to import Channel functionality. --- src/SDK/Language/Apple.php | 2 +- src/SDK/Language/Dart.php | 5 + templates/apple/Sources/Channel.swift.twig | 108 +++++++++++++++++++++ templates/dart/test/channel_test.dart.twig | 3 +- templates/flutter/lib/package.dart.twig | 1 + tests/Android14Java11Test.php | 7 +- tests/Android14Java17Test.php | 1 + tests/Android14Java8Test.php | 7 +- tests/Android5Java17Test.php | 1 + tests/AppleSwift56Test.php | 1 + tests/Base.php | 23 +++++ tests/FlutterBetaTest.php | 1 + tests/FlutterStableTest.php | 1 + tests/WebChromiumTest.php | 1 + tests/WebNodeTest.php | 1 + tests/languages/android/Tests.kt | 1 + tests/languages/node/test.js | 1 + tests/languages/web/index.html | 2 +- tests/languages/web/node.js | 2 +- 19 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 templates/apple/Sources/Channel.swift.twig diff --git a/src/SDK/Language/Apple.php b/src/SDK/Language/Apple.php index 3f409b593..08f45a604 100644 --- a/src/SDK/Language/Apple.php +++ b/src/SDK/Language/Apple.php @@ -73,7 +73,7 @@ public function getFiles(): array [ 'scope' => 'default', 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Channel.swift', - 'template' => 'swift/Sources/Channel.swift.twig', + 'template' => 'apple/Sources/Channel.swift.twig', ], [ 'scope' => 'default', diff --git a/src/SDK/Language/Dart.php b/src/SDK/Language/Dart.php index 01e197509..be3067cda 100644 --- a/src/SDK/Language/Dart.php +++ b/src/SDK/Language/Dart.php @@ -354,6 +354,11 @@ public function getFiles(): array 'destination' => '/lib/id.dart', 'template' => 'dart/lib/id.dart.twig', ], + [ + 'scope' => 'default', + 'destination' => '/lib/channel.dart', + 'template' => 'dart/lib/channel.dart.twig', + ], [ 'scope' => 'default', 'destination' => '/lib/query.dart', diff --git a/templates/apple/Sources/Channel.swift.twig b/templates/apple/Sources/Channel.swift.twig new file mode 100644 index 000000000..ba14f2f20 --- /dev/null +++ b/templates/apple/Sources/Channel.swift.twig @@ -0,0 +1,108 @@ +public class Channel { + /** + * Generate a database channel string. + * + * @param databaseId The database ID (default: "*") + * @param collectionId The collection ID (default: "*") + * @param documentId The document ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: nil) + * @returns The channel string + */ + public static func database(databaseId: String = "*", collectionId: String = "*", documentId: String = "*", action: String? = nil) -> String { + var channel = "databases.\(databaseId).collections.\(collectionId).documents.\(documentId)" + if let action = action { + channel += ".\(action)" + } + return channel + } + + /** + * Generate a tables database channel string. + * + * @param databaseId The database ID (default: "*") + * @param tableId The table ID (default: "*") + * @param rowId The row ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: nil) + * @returns The channel string + */ + public static func tablesdb(databaseId: String = "*", tableId: String = "*", rowId: String = "*", action: String? = nil) -> String { + var channel = "tablesdb.\(databaseId).tables.\(tableId).rows.\(rowId)" + if let action = action { + channel += ".\(action)" + } + return channel + } + + /** + * Generate an account channel string. + * + * @param userId The user ID (default: "*") + * @returns The channel string + */ + public static func account(userId: String = "*") -> String { + return "account.\(userId)" + } + + /** + * Generate a files channel string. + * + * @param bucketId The bucket ID (default: "*") + * @param fileId The file ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: nil) + * @returns The channel string + */ + public static func files(bucketId: String = "*", fileId: String = "*", action: String? = nil) -> String { + var channel = "buckets.\(bucketId).files.\(fileId)" + if let action = action { + channel += ".\(action)" + } + return channel + } + + /** + * Generate an executions channel string. + * + * @param functionId The function ID (default: "*") + * @param executionId The execution ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: nil) + * @returns The channel string + */ + public static func executions(functionId: String = "*", executionId: String = "*", action: String? = nil) -> String { + var channel = "functions.\(functionId).executions.\(executionId)" + if let action = action { + channel += ".\(action)" + } + return channel + } + + /** + * Generate a teams channel string. + * + * @param teamId The team ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: nil) + * @returns The channel string + */ + public static func teams(teamId: String = "*", action: String? = nil) -> String { + var channel = "teams.\(teamId)" + if let action = action { + channel += ".\(action)" + } + return channel + } + + /** + * Generate a memberships channel string. + * + * @param membershipId The membership ID (default: "*") + * @param action Optional action: "create", "update", or "delete" (default: nil) + * @returns The channel string + */ + public static func memberships(membershipId: String = "*", action: String? = nil) -> String { + var channel = "memberships.\(membershipId)" + if let action = action { + channel += ".\(action)" + } + return channel + } +} + diff --git a/templates/dart/test/channel_test.dart.twig b/templates/dart/test/channel_test.dart.twig index 2a3dd5953..db3cf04fe 100644 --- a/templates/dart/test/channel_test.dart.twig +++ b/templates/dart/test/channel_test.dart.twig @@ -108,5 +108,4 @@ void main() { 'memberships.membership1.update'); }); }); -} - +} \ No newline at end of file diff --git a/templates/flutter/lib/package.dart.twig b/templates/flutter/lib/package.dart.twig index 51965ccb9..515452fa6 100644 --- a/templates/flutter/lib/package.dart.twig +++ b/templates/flutter/lib/package.dart.twig @@ -30,6 +30,7 @@ part 'query.dart'; part 'permission.dart'; part 'role.dart'; part 'id.dart'; +part 'channel.dart'; part 'operator.dart'; {% for service in spec.services %} part 'services/{{service.name | caseSnake}}.dart'; diff --git a/tests/Android14Java11Test.php b/tests/Android14Java11Test.php index b6e114f40..1bc2f2360 100644 --- a/tests/Android14Java11Test.php +++ b/tests/Android14Java11Test.php @@ -24,15 +24,16 @@ class Android14Java11Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, - ...Base::LARGE_FILE_RESPONSES, - ...Base::LARGE_FILE_RESPONSES, - ...Base::LARGE_FILE_RESPONSES, + // ...Base::LARGE_FILE_RESPONSES, + // ...Base::LARGE_FILE_RESPONSES, + // ...Base::LARGE_FILE_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::REALTIME_RESPONSES, // ...Base::COOKIE_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, + ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES ]; } diff --git a/tests/Android14Java17Test.php b/tests/Android14Java17Test.php index aede9c7af..e6e1b7c45 100644 --- a/tests/Android14Java17Test.php +++ b/tests/Android14Java17Test.php @@ -32,6 +32,7 @@ class Android14Java17Test extends Base ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, + ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES ]; } diff --git a/tests/Android14Java8Test.php b/tests/Android14Java8Test.php index 0355e77b0..84a81ebe4 100644 --- a/tests/Android14Java8Test.php +++ b/tests/Android14Java8Test.php @@ -24,15 +24,16 @@ class Android14Java8Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, - ...Base::LARGE_FILE_RESPONSES, - ...Base::LARGE_FILE_RESPONSES, - ...Base::LARGE_FILE_RESPONSES, + // ...Base::LARGE_FILE_RESPONSES, + // ...Base::LARGE_FILE_RESPONSES, + // ...Base::LARGE_FILE_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::REALTIME_RESPONSES, // ...Base::COOKIE_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, + ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES ]; } diff --git a/tests/Android5Java17Test.php b/tests/Android5Java17Test.php index 9677e0f00..1a58bb327 100644 --- a/tests/Android5Java17Test.php +++ b/tests/Android5Java17Test.php @@ -32,6 +32,7 @@ class Android5Java17Test extends Base ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, + ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES ]; } diff --git a/tests/AppleSwift56Test.php b/tests/AppleSwift56Test.php index b4a6709f1..36852cae0 100644 --- a/tests/AppleSwift56Test.php +++ b/tests/AppleSwift56Test.php @@ -31,6 +31,7 @@ class AppleSwift56Test extends Base ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, + ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES ]; } diff --git a/tests/Base.php b/tests/Base.php index 4917f2d9a..2a2b3e17e 100644 --- a/tests/Base.php +++ b/tests/Base.php @@ -163,6 +163,29 @@ abstract class Base extends TestCase 'custom_id' ]; + protected const CHANNEL_HELPER_RESPONSES = [ + 'databases.*.collections.*.documents.*', + 'databases.db1.collections.col1.documents.doc1', + 'databases.db1.collections.col1.documents.doc1.create', + 'tablesdb.*.tables.*.rows.*', + 'tablesdb.db1.tables.table1.rows.row1', + 'tablesdb.db1.tables.table1.rows.row1.update', + 'account.*', + 'account.user123', + 'buckets.*.files.*', + 'buckets.bucket1.files.file1', + 'buckets.bucket1.files.file1.delete', + 'functions.*.executions.*', + 'functions.func1.executions.exec1', + 'functions.func1.executions.exec1.create', + 'teams.*', + 'teams.team1', + 'teams.team1.create', + 'memberships.*', + 'memberships.membership1', + 'memberships.membership1.update', + ]; + protected const OPERATOR_HELPER_RESPONSES = [ '{"method":"increment","values":[1]}', '{"method":"increment","values":[5,100]}', diff --git a/tests/FlutterBetaTest.php b/tests/FlutterBetaTest.php index 366f3d132..32b99cf55 100644 --- a/tests/FlutterBetaTest.php +++ b/tests/FlutterBetaTest.php @@ -31,6 +31,7 @@ class FlutterBetaTest extends Base ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, + ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES ]; } diff --git a/tests/FlutterStableTest.php b/tests/FlutterStableTest.php index 62521f780..1c98f9ae3 100644 --- a/tests/FlutterStableTest.php +++ b/tests/FlutterStableTest.php @@ -31,6 +31,7 @@ class FlutterStableTest extends Base ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, + ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES ]; } diff --git a/tests/WebChromiumTest.php b/tests/WebChromiumTest.php index 8e609e383..9a85edb8d 100644 --- a/tests/WebChromiumTest.php +++ b/tests/WebChromiumTest.php @@ -35,6 +35,7 @@ class WebChromiumTest extends Base ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, + ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES ]; } diff --git a/tests/WebNodeTest.php b/tests/WebNodeTest.php index dc8e48275..8e09dcdf6 100644 --- a/tests/WebNodeTest.php +++ b/tests/WebNodeTest.php @@ -35,6 +35,7 @@ class WebNodeTest extends Base ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, + ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES ]; } diff --git a/tests/languages/android/Tests.kt b/tests/languages/android/Tests.kt index eaed5dd53..579bc0019 100644 --- a/tests/languages/android/Tests.kt +++ b/tests/languages/android/Tests.kt @@ -7,6 +7,7 @@ import io.appwrite.exceptions.AppwriteException import io.appwrite.Permission import io.appwrite.Role import io.appwrite.ID +import io.appwrite.Channel import io.appwrite.Query import io.appwrite.Operator import io.appwrite.Condition diff --git a/tests/languages/node/test.js b/tests/languages/node/test.js index c52aafd28..e0d43bb6b 100644 --- a/tests/languages/node/test.js +++ b/tests/languages/node/test.js @@ -4,6 +4,7 @@ const { Query, Role, ID, + Channel, Operator, Condition, MockType, diff --git a/tests/languages/web/index.html b/tests/languages/web/index.html index 8aced2b34..f85cbad04 100644 --- a/tests/languages/web/index.html +++ b/tests/languages/web/index.html @@ -21,7 +21,7 @@ let responseRealtime = 'Realtime failed!'; // Init SDK - const { Client, Foo, Bar, General, Realtime, Query, Permission, Role, ID, Operator, Condition, MockType } = Appwrite; + const { Client, Foo, Bar, General, Realtime, Query, Permission, Role, ID, Channel, Operator, Condition, MockType } = Appwrite; const client = new Client(); const foo = new Foo(client); diff --git a/tests/languages/web/node.js b/tests/languages/web/node.js index 8e82b3478..45bf89fee 100644 --- a/tests/languages/web/node.js +++ b/tests/languages/web/node.js @@ -1,4 +1,4 @@ -const { Client, Foo, Bar, General, Query, Permission, Role, ID, Operator, Condition, MockType } = require('./dist/cjs/sdk.js'); +const { Client, Foo, Bar, General, Query, Permission, Role, ID, Channel, Operator, Condition, MockType } = require('./dist/cjs/sdk.js'); async function start() { let response; From 5d19169d9914fb7d415622218654ad9096527d2a Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 18 Dec 2025 23:24:58 +0530 Subject: [PATCH 3/6] reverted changes --- tests/Android14Java11Test.php | 6 +++--- tests/Android14Java8Test.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Android14Java11Test.php b/tests/Android14Java11Test.php index 1bc2f2360..b01e4ec93 100644 --- a/tests/Android14Java11Test.php +++ b/tests/Android14Java11Test.php @@ -24,9 +24,9 @@ class Android14Java11Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, - // ...Base::LARGE_FILE_RESPONSES, - // ...Base::LARGE_FILE_RESPONSES, - // ...Base::LARGE_FILE_RESPONSES, + ...Base::LARGE_FILE_RESPONSES, + ...Base::LARGE_FILE_RESPONSES, + ...Base::LARGE_FILE_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::REALTIME_RESPONSES, // ...Base::COOKIE_RESPONSES, diff --git a/tests/Android14Java8Test.php b/tests/Android14Java8Test.php index 84a81ebe4..4bb086e25 100644 --- a/tests/Android14Java8Test.php +++ b/tests/Android14Java8Test.php @@ -24,9 +24,9 @@ class Android14Java8Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, - // ...Base::LARGE_FILE_RESPONSES, - // ...Base::LARGE_FILE_RESPONSES, - // ...Base::LARGE_FILE_RESPONSES, + ...Base::LARGE_FILE_RESPONSES, + ...Base::LARGE_FILE_RESPONSES, + ...Base::LARGE_FILE_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::REALTIME_RESPONSES, // ...Base::COOKIE_RESPONSES, From b18671f078e7f64aa18050fea94b66d13e093806 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 18 Dec 2025 23:54:20 +0530 Subject: [PATCH 4/6] Refactor Channel references to use NIOCore.Channel and remove unused Channel helper tests from node and swift --- templates/apple/Sources/Channel.swift.twig | 2 + templates/swift/Sources/Channel.swift.twig | 108 ------------------ .../WebSockets/WebSocketClient.swift.twig | 20 ++-- .../WebSocketClientDelegate.swift.twig | 8 +- tests/languages/node/test.js | 23 ---- tests/languages/swift/Tests.swift | 22 ---- 6 files changed, 16 insertions(+), 167 deletions(-) delete mode 100644 templates/swift/Sources/Channel.swift.twig diff --git a/templates/apple/Sources/Channel.swift.twig b/templates/apple/Sources/Channel.swift.twig index ba14f2f20..aec5ada8f 100644 --- a/templates/apple/Sources/Channel.swift.twig +++ b/templates/apple/Sources/Channel.swift.twig @@ -1,3 +1,5 @@ +import Foundation + public class Channel { /** * Generate a database channel string. diff --git a/templates/swift/Sources/Channel.swift.twig b/templates/swift/Sources/Channel.swift.twig deleted file mode 100644 index ba14f2f20..000000000 --- a/templates/swift/Sources/Channel.swift.twig +++ /dev/null @@ -1,108 +0,0 @@ -public class Channel { - /** - * Generate a database channel string. - * - * @param databaseId The database ID (default: "*") - * @param collectionId The collection ID (default: "*") - * @param documentId The document ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: nil) - * @returns The channel string - */ - public static func database(databaseId: String = "*", collectionId: String = "*", documentId: String = "*", action: String? = nil) -> String { - var channel = "databases.\(databaseId).collections.\(collectionId).documents.\(documentId)" - if let action = action { - channel += ".\(action)" - } - return channel - } - - /** - * Generate a tables database channel string. - * - * @param databaseId The database ID (default: "*") - * @param tableId The table ID (default: "*") - * @param rowId The row ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: nil) - * @returns The channel string - */ - public static func tablesdb(databaseId: String = "*", tableId: String = "*", rowId: String = "*", action: String? = nil) -> String { - var channel = "tablesdb.\(databaseId).tables.\(tableId).rows.\(rowId)" - if let action = action { - channel += ".\(action)" - } - return channel - } - - /** - * Generate an account channel string. - * - * @param userId The user ID (default: "*") - * @returns The channel string - */ - public static func account(userId: String = "*") -> String { - return "account.\(userId)" - } - - /** - * Generate a files channel string. - * - * @param bucketId The bucket ID (default: "*") - * @param fileId The file ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: nil) - * @returns The channel string - */ - public static func files(bucketId: String = "*", fileId: String = "*", action: String? = nil) -> String { - var channel = "buckets.\(bucketId).files.\(fileId)" - if let action = action { - channel += ".\(action)" - } - return channel - } - - /** - * Generate an executions channel string. - * - * @param functionId The function ID (default: "*") - * @param executionId The execution ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: nil) - * @returns The channel string - */ - public static func executions(functionId: String = "*", executionId: String = "*", action: String? = nil) -> String { - var channel = "functions.\(functionId).executions.\(executionId)" - if let action = action { - channel += ".\(action)" - } - return channel - } - - /** - * Generate a teams channel string. - * - * @param teamId The team ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: nil) - * @returns The channel string - */ - public static func teams(teamId: String = "*", action: String? = nil) -> String { - var channel = "teams.\(teamId)" - if let action = action { - channel += ".\(action)" - } - return channel - } - - /** - * Generate a memberships channel string. - * - * @param membershipId The membership ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: nil) - * @returns The channel string - */ - public static func memberships(membershipId: String = "*", action: String? = nil) -> String { - var channel = "memberships.\(membershipId)" - if let action = action { - channel += ".\(action)" - } - return channel - } -} - diff --git a/templates/swift/Sources/WebSockets/WebSocketClient.swift.twig b/templates/swift/Sources/WebSockets/WebSocketClient.swift.twig index 0e6c3e7ee..7c004d295 100644 --- a/templates/swift/Sources/WebSockets/WebSocketClient.swift.twig +++ b/templates/swift/Sources/WebSockets/WebSocketClient.swift.twig @@ -32,7 +32,7 @@ public class WebSocketClient { private let channelQueue = DispatchQueue(label: WEBSOCKET_CHANNEL_QUEUE) private let threadGroupQueue = DispatchQueue(label: WEBSOCKET_THREAD_QUEUE) - var channel: Channel? { + var channel: NIOCore.Channel? { get { return channelQueue.sync { _channel } } @@ -40,7 +40,7 @@ public class WebSocketClient { channelQueue.sync { _channel = newValue } } } - private var _channel: Channel? = nil + private var _channel: NIOCore.Channel? = nil var threadGroup: MultiThreadedEventLoopGroup? { get { @@ -61,8 +61,8 @@ public class WebSocketClient { // MARK: - Stored callbacks - private var _openCallback: (Channel) -> Void = { _ in } - var onOpen: (Channel) -> Void { + private var _openCallback: (NIOCore.Channel) -> Void = { _ in } + var onOpen: (NIOCore.Channel) -> Void { get { return locker.sync { return _openCallback @@ -75,8 +75,8 @@ public class WebSocketClient { } } - private var _closeCallback: (Channel, Data) -> Void = { _,_ in } - var onClose: (Channel, Data) -> Void { + private var _closeCallback: (NIOCore.Channel, Data) -> Void = { _,_ in } + var onClose: (NIOCore.Channel, Data) -> Void { get { return locker.sync { return _closeCallback @@ -137,7 +137,7 @@ public class WebSocketClient { /// /// - parameters: /// - callback: Callback to fie when a WebSocket connection is opened - public func onOpen(_ callback: @escaping (Channel) -> Void) { + public func onOpen(_ callback: @escaping (NIOCore.Channel) -> Void) { onOpen = callback } @@ -161,7 +161,7 @@ public class WebSocketClient { /// /// - parameters: /// - callback: Callback to fie when a WebSocket close message is received - public func onClose(_ callback: @escaping (Channel, Data) -> Void) { + public func onClose(_ callback: @escaping (NIOCore.Channel, Data) -> Void) { onClose = callback } @@ -262,7 +262,7 @@ public class WebSocketClient { .get() } - private func openChannel(channel: Channel) -> EventLoopFuture { + private func openChannel(channel: NIOCore.Channel) -> EventLoopFuture { let httpHandler = HTTPHandler(client: self, headers: headers) let basicUpgrader = NIOWebSocketClientUpgrader( @@ -290,7 +290,7 @@ public class WebSocketClient { } } - private func upgradePipelineHandler(channel: Channel, response: HTTPResponseHead) -> EventLoopFuture { + private func upgradePipelineHandler(channel: NIOCore.Channel, response: HTTPResponseHead) -> EventLoopFuture { let handler = MessageHandler(client: self) if response.status == .switchingProtocols { diff --git a/templates/swift/Sources/WebSockets/WebSocketClientDelegate.swift.twig b/templates/swift/Sources/WebSockets/WebSocketClientDelegate.swift.twig index d1556acb9..d6327e0d9 100644 --- a/templates/swift/Sources/WebSockets/WebSocketClientDelegate.swift.twig +++ b/templates/swift/Sources/WebSockets/WebSocketClientDelegate.swift.twig @@ -4,22 +4,22 @@ import NIOHTTP1 /// Handles messages received by a connected WebSocket server. public protocol WebSocketClientDelegate : AnyObject { - func onOpen(channel: Channel) + func onOpen(channel: NIOCore.Channel) func onMessage(text: String) throws func onMessage(data: Data) throws - func onClose(channel: Channel, data: Data) + func onClose(channel: NIOCore.Channel, data: Data) func onError(error: Swift.Error?, status: HTTPResponseStatus?) throws } // Add empty default implementations extension WebSocketClientDelegate { - public func onOpen(channel: Channel) { + public func onOpen(channel: NIOCore.Channel) { } public func onMessage(text: String) { } public func onMessage(data: Data) { } - public func onClose(channel: Channel, data: Data) { + public func onClose(channel: NIOCore.Channel, data: Data) { } public func onError(error: Swift.Error?, status: HTTPResponseStatus?) throws { } diff --git a/tests/languages/node/test.js b/tests/languages/node/test.js index e0d43bb6b..ee0fd703f 100644 --- a/tests/languages/node/test.js +++ b/tests/languages/node/test.js @@ -4,7 +4,6 @@ const { Query, Role, ID, - Channel, Operator, Condition, MockType, @@ -327,28 +326,6 @@ async function start() { console.log(ID.unique()); console.log(ID.custom('custom_id')); - // Channel helper tests - console.log(Channel.database()); - console.log(Channel.database('db1', 'col1', 'doc1')); - console.log(Channel.database('db1', 'col1', 'doc1', 'create')); - console.log(Channel.tablesdb()); - console.log(Channel.tablesdb('db1', 'table1', 'row1')); - console.log(Channel.tablesdb('db1', 'table1', 'row1', 'update')); - console.log(Channel.account()); - console.log(Channel.account('user123')); - console.log(Channel.files()); - console.log(Channel.files('bucket1', 'file1')); - console.log(Channel.files('bucket1', 'file1', 'delete')); - console.log(Channel.executions()); - console.log(Channel.executions('func1', 'exec1')); - console.log(Channel.executions('func1', 'exec1', 'create')); - console.log(Channel.teams()); - console.log(Channel.teams('team1')); - console.log(Channel.teams('team1', 'create')); - console.log(Channel.memberships()); - console.log(Channel.memberships('membership1')); - console.log(Channel.memberships('membership1', 'update')); - // Operator helper tests console.log(Operator.increment(1)); console.log(Operator.increment(5, 100)); diff --git a/tests/languages/swift/Tests.swift b/tests/languages/swift/Tests.swift index d682ff60d..8719dc646 100644 --- a/tests/languages/swift/Tests.swift +++ b/tests/languages/swift/Tests.swift @@ -235,28 +235,6 @@ class Tests: XCTestCase { print(ID.unique()) print(ID.custom("custom_id")) - // Channel helper tests - print(Channel.database()) - print(Channel.database(databaseId: "db1", collectionId: "col1", documentId: "doc1")) - print(Channel.database(databaseId: "db1", collectionId: "col1", documentId: "doc1", action: "create")) - print(Channel.tablesdb()) - print(Channel.tablesdb(databaseId: "db1", tableId: "table1", rowId: "row1")) - print(Channel.tablesdb(databaseId: "db1", tableId: "table1", rowId: "row1", action: "update")) - print(Channel.account()) - print(Channel.account(userId: "user123")) - print(Channel.files()) - print(Channel.files(bucketId: "bucket1", fileId: "file1")) - print(Channel.files(bucketId: "bucket1", fileId: "file1", action: "delete")) - print(Channel.executions()) - print(Channel.executions(functionId: "func1", executionId: "exec1")) - print(Channel.executions(functionId: "func1", executionId: "exec1", action: "create")) - print(Channel.teams()) - print(Channel.teams(teamId: "team1")) - print(Channel.teams(teamId: "team1", action: "create")) - print(Channel.memberships()) - print(Channel.memberships(membershipId: "membership1")) - print(Channel.memberships(membershipId: "membership1", action: "update")) - // Operator helper tests print(Operator.increment(1)) print(Operator.increment(5, max: 100)) From fd57b6560b7589d8ed13dab823ff9fe4fca365d5 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Fri, 19 Dec 2025 18:24:49 +0530 Subject: [PATCH 5/6] Refractor channel to use builder pattern --- src/SDK/Language/Flutter.php | 2 +- .../src/main/java/io/package/Channel.kt.twig | 218 ++++++++----- .../java/io/package/services/Realtime.kt.twig | 31 ++ templates/apple/Sources/Channel.swift.twig | 289 +++++++++++++---- .../Sources/Services/Realtime.swift.twig | 56 +++- templates/dart/lib/channel.dart.twig | 116 ------- templates/flutter/lib/channel.dart.twig | 239 ++++++++++++++ templates/flutter/lib/src/realtime.dart.twig | 10 +- .../flutter/lib/src/realtime_base.dart.twig | 2 +- .../lib/src/realtime_browser.dart.twig | 2 +- .../flutter/lib/src/realtime_io.dart.twig | 2 +- .../flutter/lib/src/realtime_mixin.dart.twig | 17 +- templates/web/src/channel.ts.twig | 295 +++++++++++++----- templates/web/src/client.ts.twig | 33 +- templates/web/src/services/realtime.ts.twig | 47 ++- tests/languages/android/Tests.kt | 36 +-- tests/languages/apple/Tests.swift | 38 +-- tests/languages/flutter/tests.dart | 38 +-- tests/languages/web/index.html | 36 +-- tests/languages/web/node.js | 36 +-- 20 files changed, 1091 insertions(+), 452 deletions(-) delete mode 100644 templates/dart/lib/channel.dart.twig create mode 100644 templates/flutter/lib/channel.dart.twig diff --git a/src/SDK/Language/Flutter.php b/src/SDK/Language/Flutter.php index 613720c09..0643939a3 100644 --- a/src/SDK/Language/Flutter.php +++ b/src/SDK/Language/Flutter.php @@ -83,7 +83,7 @@ public function getFiles(): array [ 'scope' => 'default', 'destination' => '/lib/channel.dart', - 'template' => 'dart/lib/channel.dart.twig', + 'template' => 'flutter/lib/channel.dart.twig', ], [ 'scope' => 'default', diff --git a/templates/android/library/src/main/java/io/package/Channel.kt.twig b/templates/android/library/src/main/java/io/package/Channel.kt.twig index 10f9fcd41..4fe27e432 100644 --- a/templates/android/library/src/main/java/io/package/Channel.kt.twig +++ b/templates/android/library/src/main/java/io/package/Channel.kt.twig @@ -1,115 +1,189 @@ package {{ sdk.namespace | caseDot }} -/** - * Helper class to generate channel strings for realtime subscriptions. - */ +class ResolvedChannel(private val value: String) { + override fun toString(): String { + return value + } +} + +abstract class ActionChannel(protected val base: String) { + fun create(): ResolvedChannel { + return ResolvedChannel("$base.create") + } + + fun update(): ResolvedChannel { + return ResolvedChannel("$base.update") + } + + fun delete(): ResolvedChannel { + return ResolvedChannel("$base.delete") + } + + override fun toString(): String { + return base + } +} + +class DocumentChannel(base: String) : ActionChannel(base) + +class CollectionChannel( + private val databaseId: String, + private val collectionId: String +) { + private val base = "databases.$databaseId.collections.$collectionId" + + fun document(documentId: String = "*"): DocumentChannel { + return DocumentChannel("$base.documents.$documentId") + } + + override fun toString(): String { + return base + } +} + +class DatabaseChannel(private val databaseId: String) { + private val base = "databases.$databaseId" + + fun collection(collectionId: String = "*"): CollectionChannel { + return CollectionChannel(databaseId, collectionId) + } + + override fun toString(): String { + return base + } +} + +class RowChannel(base: String) : ActionChannel(base) + +class TableChannel( + private val databaseId: String, + private val tableId: String +) { + private val base = "tablesdb.$databaseId.tables.$tableId" + + fun row(rowId: String = "*"): RowChannel { + return RowChannel("$base.rows.$rowId") + } + + override fun toString(): String { + return base + } +} + +class TablesDBChannel(private val databaseId: String) { + private val base = "tablesdb.$databaseId" + + fun table(tableId: String = "*"): TableChannel { + return TableChannel(databaseId, tableId) + } + + override fun toString(): String { + return base + } +} + +class FileChannel(base: String) : ActionChannel(base) + +class BucketChannel(private val bucketId: String) { + private val base = "buckets.$bucketId" + + fun file(fileId: String = "*"): FileChannel { + return FileChannel("$base.files.$fileId") + } + + override fun toString(): String { + return base + } +} + +class ExecutionChannel(base: String) : ActionChannel(base) + +class FunctionChannel(private val functionId: String) { + private val base = "functions.$functionId" + + fun execution(executionId: String = "*"): ExecutionChannel { + return ExecutionChannel("$base.executions.$executionId") + } + + override fun toString(): String { + return base + } +} + +class TeamChannel(teamId: String = "*") : ActionChannel("teams.$teamId") + +class MembershipChannel(membershipId: String = "*") : ActionChannel("memberships.$membershipId") + class Channel { companion object { - /** - * Generate a database channel string. + * Generate a database channel builder. * * @param databaseId The database ID (default: "*") - * @param collectionId The collection ID (default: "*") - * @param documentId The document ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: null) - * @returns The channel string + * @returns DatabaseChannel */ - fun database(databaseId: String = "*", collectionId: String = "*", documentId: String = "*", action: String? = null): String { - var channel = "databases.$databaseId.collections.$collectionId.documents.$documentId" - if (action != null) { - channel += ".$action" - } - return channel + fun database(databaseId: String = "*"): DatabaseChannel { + return DatabaseChannel(databaseId) } /** - * Generate a tables database channel string. + * Generate a tables database channel builder. * * @param databaseId The database ID (default: "*") - * @param tableId The table ID (default: "*") - * @param rowId The row ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: null) - * @returns The channel string + * @returns TablesDBChannel */ - fun tablesdb(databaseId: String = "*", tableId: String = "*", rowId: String = "*", action: String? = null): String { - var channel = "tablesdb.$databaseId.tables.$tableId.rows.$rowId" - if (action != null) { - channel += ".$action" - } - return channel + fun tablesdb(databaseId: String = "*"): TablesDBChannel { + return TablesDBChannel(databaseId) } /** - * Generate an account channel string. + * Generate a buckets channel builder. * - * @param userId The user ID (default: "*") - * @returns The channel string + * @param bucketId The bucket ID (default: "*") + * @returns BucketChannel */ - fun account(userId: String = "*"): String { - return "account.$userId" + fun buckets(bucketId: String = "*"): BucketChannel { + return BucketChannel(bucketId) } /** - * Generate a files channel string. + * Generate a functions channel builder. * - * @param bucketId The bucket ID (default: "*") - * @param fileId The file ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: null) - * @returns The channel string + * @param functionId The function ID (default: "*") + * @returns FunctionChannel */ - fun files(bucketId: String = "*", fileId: String = "*", action: String? = null): String { - var channel = "buckets.$bucketId.files.$fileId" - if (action != null) { - channel += ".$action" - } - return channel + fun functions(functionId: String = "*"): FunctionChannel { + return FunctionChannel(functionId) } /** - * Generate an executions channel string. + * Generate a teams channel builder. * - * @param functionId The function ID (default: "*") - * @param executionId The execution ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: null) - * @returns The channel string + * @param teamId The team ID (default: "*") + * @returns TeamChannel */ - fun executions(functionId: String = "*", executionId: String = "*", action: String? = null): String { - var channel = "functions.$functionId.executions.$executionId" - if (action != null) { - channel += ".$action" - } - return channel + fun teams(teamId: String = "*"): TeamChannel { + return TeamChannel(teamId) } /** - * Generate a teams channel string. + * Generate a memberships channel builder. * - * @param teamId The team ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: null) - * @returns The channel string + * @param membershipId The membership ID (default: "*") + * @returns MembershipChannel */ - fun teams(teamId: String = "*", action: String? = null): String { - var channel = "teams.$teamId" - if (action != null) { - channel += ".$action" - } - return channel + fun memberships(membershipId: String = "*"): MembershipChannel { + return MembershipChannel(membershipId) } /** - * Generate a memberships channel string. + * Generate an account channel string. * - * @param membershipId The membership ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: null) + * @param userId The user ID (default: "*") * @returns The channel string */ - fun memberships(membershipId: String = "*", action: String? = null): String { - var channel = "memberships.$membershipId" - if (action != null) { - channel += ".$action" - } - return channel + fun account(userId: String = "*"): String { + return "account.$userId" } } } diff --git a/templates/android/library/src/main/java/io/package/services/Realtime.kt.twig b/templates/android/library/src/main/java/io/package/services/Realtime.kt.twig index 26de08d0e..2e887dfe3 100644 --- a/templates/android/library/src/main/java/io/package/services/Realtime.kt.twig +++ b/templates/android/library/src/main/java/io/package/services/Realtime.kt.twig @@ -109,6 +109,25 @@ class Realtime(client: Client) : Service(client), CoroutineScope { else -> 60000L } + /** + * Convert channel value to string + */ + private fun channelToString(channel: Any): String { + return when (channel) { + is String -> channel + else -> channel.toString() + } + } + + fun subscribe( + vararg channels: Any, + callback: (RealtimeResponseEvent) -> Unit, + ) = subscribe( + channels = channels.map { channelToString(it) }.toTypedArray(), + Any::class.java, + callback + ) + fun subscribe( vararg channels: String, callback: (RealtimeResponseEvent) -> Unit, @@ -118,6 +137,18 @@ class Realtime(client: Client) : Service(client), CoroutineScope { callback ) + fun subscribe( + vararg channels: Any, + payloadType: Class, + callback: (RealtimeResponseEvent) -> Unit, + ): RealtimeSubscription { + return subscribe( + channels = channels.map { channelToString(it) }.toTypedArray(), + payloadType = payloadType, + callback = callback + ) + } + fun subscribe( vararg channels: String, payloadType: Class, diff --git a/templates/apple/Sources/Channel.swift.twig b/templates/apple/Sources/Channel.swift.twig index aec5ada8f..599d9d595 100644 --- a/templates/apple/Sources/Channel.swift.twig +++ b/templates/apple/Sources/Channel.swift.twig @@ -1,110 +1,261 @@ import Foundation +public class ResolvedChannel { + private let value: String + + init(value: String) { + self.value = value + } + + public func toString() -> String { + return value + } +} + +open class ActionChannel { + let base: String + + init(base: String) { + self.base = base + } + + public func create() -> ResolvedChannel { + return ResolvedChannel(value: "\(base).create") + } + + public func update() -> ResolvedChannel { + return ResolvedChannel(value: "\(base).update") + } + + public func delete() -> ResolvedChannel { + return ResolvedChannel(value: "\(base).delete") + } + + public func toString() -> String { + return base + } +} + +/** + * ---------------------------- + * Database → Collection → Document + * ---------------------------- + */ +public class DocumentChannel: ActionChannel {} + +public class CollectionChannel { + private let base: String + private let databaseId: String + private let collectionId: String + + init(databaseId: String, collectionId: String) { + self.databaseId = databaseId + self.collectionId = collectionId + self.base = "databases.\(databaseId).collections.\(collectionId)" + } + + public func document(_ documentId: String = "*") -> DocumentChannel { + return DocumentChannel(base: "\(base).documents.\(documentId)") + } + + public func toString() -> String { + return base + } +} + +public class DatabaseChannel { + private let base: String + private let databaseId: String + + init(databaseId: String) { + self.databaseId = databaseId + self.base = "databases.\(databaseId)" + } + + public func collection(_ collectionId: String = "*") -> CollectionChannel { + return CollectionChannel(databaseId: databaseId, collectionId: collectionId) + } + + public func toString() -> String { + return base + } +} + +/** + * ---------------------------- + * TablesDB → Table → Row + * ---------------------------- + */ +public class RowChannel: ActionChannel {} + +public class TableChannel { + private let base: String + private let databaseId: String + private let tableId: String + + init(databaseId: String, tableId: String) { + self.databaseId = databaseId + self.tableId = tableId + self.base = "tablesdb.\(databaseId).tables.\(tableId)" + } + + public func row(_ rowId: String = "*") -> RowChannel { + return RowChannel(base: "\(base).rows.\(rowId)") + } + + public func toString() -> String { + return base + } +} + +public class TablesDBChannel { + private let base: String + private let databaseId: String + + init(databaseId: String) { + self.databaseId = databaseId + self.base = "tablesdb.\(databaseId)" + } + + public func table(_ tableId: String = "*") -> TableChannel { + return TableChannel(databaseId: databaseId, tableId: tableId) + } + + public func toString() -> String { + return base + } +} + +/** + * ---------------------------- + * Buckets → File + * ---------------------------- + */ +public class FileChannel: ActionChannel {} + +public class BucketChannel { + private let base: String + private let bucketId: String + + init(bucketId: String) { + self.bucketId = bucketId + self.base = "buckets.\(bucketId)" + } + + public func file(_ fileId: String = "*") -> FileChannel { + return FileChannel(base: "\(base).files.\(fileId)") + } + + public func toString() -> String { + return base + } +} + +/** + * ---------------------------- + * Functions → Execution + * ---------------------------- + */ +public class ExecutionChannel: ActionChannel {} + +public class FunctionChannel { + private let base: String + private let functionId: String + + init(functionId: String) { + self.functionId = functionId + self.base = "functions.\(functionId)" + } + + public func execution(_ executionId: String = "*") -> ExecutionChannel { + return ExecutionChannel(base: "\(base).executions.\(executionId)") + } + + public func toString() -> String { + return base + } +} + +public class TeamChannel: ActionChannel { + init(teamId: String = "*") { + super.init(base: "teams.\(teamId)") + } +} + +public class MembershipChannel: ActionChannel { + init(membershipId: String = "*") { + super.init(base: "memberships.\(membershipId)") + } +} + public class Channel { /** - * Generate a database channel string. + * Generate a database channel builder. * * @param databaseId The database ID (default: "*") - * @param collectionId The collection ID (default: "*") - * @param documentId The document ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: nil) - * @returns The channel string + * @returns DatabaseChannel */ - public static func database(databaseId: String = "*", collectionId: String = "*", documentId: String = "*", action: String? = nil) -> String { - var channel = "databases.\(databaseId).collections.\(collectionId).documents.\(documentId)" - if let action = action { - channel += ".\(action)" - } - return channel + public static func database(_ databaseId: String = "*") -> DatabaseChannel { + return DatabaseChannel(databaseId: databaseId) } /** - * Generate a tables database channel string. + * Generate a tables database channel builder. * * @param databaseId The database ID (default: "*") - * @param tableId The table ID (default: "*") - * @param rowId The row ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: nil) - * @returns The channel string + * @returns TablesDBChannel */ - public static func tablesdb(databaseId: String = "*", tableId: String = "*", rowId: String = "*", action: String? = nil) -> String { - var channel = "tablesdb.\(databaseId).tables.\(tableId).rows.\(rowId)" - if let action = action { - channel += ".\(action)" - } - return channel + public static func tablesdb(_ databaseId: String = "*") -> TablesDBChannel { + return TablesDBChannel(databaseId: databaseId) } /** - * Generate an account channel string. + * Generate a buckets channel builder. * - * @param userId The user ID (default: "*") - * @returns The channel string + * @param bucketId The bucket ID (default: "*") + * @returns BucketChannel */ - public static func account(userId: String = "*") -> String { - return "account.\(userId)" + public static func buckets(_ bucketId: String = "*") -> BucketChannel { + return BucketChannel(bucketId: bucketId) } /** - * Generate a files channel string. + * Generate a functions channel builder. * - * @param bucketId The bucket ID (default: "*") - * @param fileId The file ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: nil) - * @returns The channel string + * @param functionId The function ID (default: "*") + * @returns FunctionChannel */ - public static func files(bucketId: String = "*", fileId: String = "*", action: String? = nil) -> String { - var channel = "buckets.\(bucketId).files.\(fileId)" - if let action = action { - channel += ".\(action)" - } - return channel + public static func functions(_ functionId: String = "*") -> FunctionChannel { + return FunctionChannel(functionId: functionId) } /** - * Generate an executions channel string. + * Generate a teams channel builder. * - * @param functionId The function ID (default: "*") - * @param executionId The execution ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: nil) - * @returns The channel string + * @param teamId The team ID (default: "*") + * @returns TeamChannel */ - public static func executions(functionId: String = "*", executionId: String = "*", action: String? = nil) -> String { - var channel = "functions.\(functionId).executions.\(executionId)" - if let action = action { - channel += ".\(action)" - } - return channel + public static func teams(_ teamId: String = "*") -> TeamChannel { + return TeamChannel(teamId: teamId) } /** - * Generate a teams channel string. + * Generate a memberships channel builder. * - * @param teamId The team ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: nil) - * @returns The channel string + * @param membershipId The membership ID (default: "*") + * @returns MembershipChannel */ - public static func teams(teamId: String = "*", action: String? = nil) -> String { - var channel = "teams.\(teamId)" - if let action = action { - channel += ".\(action)" - } - return channel + public static func memberships(_ membershipId: String = "*") -> MembershipChannel { + return MembershipChannel(membershipId: membershipId) } /** - * Generate a memberships channel string. + * Generate an account channel string. * - * @param membershipId The membership ID (default: "*") - * @param action Optional action: "create", "update", or "delete" (default: nil) + * @param userId The user ID (default: "*") * @returns The channel string */ - public static func memberships(membershipId: String = "*", action: String? = nil) -> String { - var channel = "memberships.\(membershipId)" - if let action = action { - channel += ".\(action)" - } - return channel + public static func account(_ userId: String = "*") -> String { + return "account.\(userId)" } } - diff --git a/templates/apple/Sources/Services/Realtime.swift.twig b/templates/apple/Sources/Services/Realtime.swift.twig index 5c2c2c401..a66f00959 100644 --- a/templates/apple/Sources/Services/Realtime.swift.twig +++ b/templates/apple/Sources/Services/Realtime.swift.twig @@ -3,6 +3,33 @@ import AsyncHTTPClient import NIO import NIOHTTP1 +/** + * Protocol for channel values that can be converted to strings + */ +public protocol ChannelValue { + func toString() -> String +} + +extension String: ChannelValue { + public func toString() -> String { + return self + } +} + +extension ResolvedChannel: ChannelValue {} +extension DatabaseChannel: ChannelValue {} +extension CollectionChannel: ChannelValue {} +extension DocumentChannel: ChannelValue {} +extension TablesDBChannel: ChannelValue {} +extension TableChannel: ChannelValue {} +extension RowChannel: ChannelValue {} +extension BucketChannel: ChannelValue {} +extension FileChannel: ChannelValue {} +extension FunctionChannel: ChannelValue {} +extension ExecutionChannel: ChannelValue {} +extension TeamChannel: ChannelValue {} +extension MembershipChannel: ChannelValue {} + open class Realtime : Service { private let TYPE_ERROR = "error" @@ -117,8 +144,15 @@ open class Realtime : Service { } } + /** + * Convert channel value to string + */ + private func channelToString(_ channel: ChannelValue) -> String { + return channel.toString() + } + public func subscribe( - channel: String, + channel: ChannelValue, callback: @escaping (RealtimeResponseEvent) -> Void ) async throws -> RealtimeSubscription { return try await subscribe( @@ -129,23 +163,35 @@ open class Realtime : Service { } public func subscribe( - channels: Set, + channels: [ChannelValue], callback: @escaping (RealtimeResponseEvent) -> Void ) async throws -> RealtimeSubscription { return try await subscribe( - channels: channels, + channels: Set(channels.map { channelToString($0) }), payloadType: String.self, callback: callback ) } public func subscribe( - channel: String, + channel: ChannelValue, payloadType: T.Type, callback: @escaping (RealtimeResponseEvent) -> Void ) async throws -> RealtimeSubscription { return try await subscribe( - channels: [channel], + channels: Set([channelToString(channel)]), + payloadType: T.self, + callback: callback + ) + } + + public func subscribe( + channels: [ChannelValue], + payloadType: T.Type, + callback: @escaping (RealtimeResponseEvent) -> Void + ) async throws -> RealtimeSubscription { + return try await subscribe( + channels: Set(channels.map { channelToString($0) }), payloadType: T.self, callback: callback ) diff --git a/templates/dart/lib/channel.dart.twig b/templates/dart/lib/channel.dart.twig deleted file mode 100644 index 7966dfacb..000000000 --- a/templates/dart/lib/channel.dart.twig +++ /dev/null @@ -1,116 +0,0 @@ -part of '{{ language.params.packageName }}.dart'; - -/// Helper class to generate channel strings for realtime subscriptions. -class Channel { - Channel._(); - - /// Generate a database channel string. - /// - /// [databaseId] The database ID (default: '*') - /// [collectionId] The collection ID (default: '*') - /// [documentId] The document ID (default: '*') - /// [action] Optional action: 'create', 'update', or 'delete' (default: null) - static String database({ - String databaseId = '*', - String collectionId = '*', - String documentId = '*', - String? action, - }) { - String channel = 'databases.$databaseId.collections.$collectionId.documents.$documentId'; - if (action != null) { - channel += '.$action'; - } - return channel; - } - - /// Generate a tables database channel string. - /// - /// [databaseId] The database ID (default: '*') - /// [tableId] The table ID (default: '*') - /// [rowId] The row ID (default: '*') - /// [action] Optional action: 'create', 'update', or 'delete' (default: null) - static String tablesdb({ - String databaseId = '*', - String tableId = '*', - String rowId = '*', - String? action, - }) { - String channel = 'tablesdb.$databaseId.tables.$tableId.rows.$rowId'; - if (action != null) { - channel += '.$action'; - } - return channel; - } - - /// Generate an account channel string. - /// - /// [userId] The user ID (default: '*') - static String account({String userId = '*'}) { - return 'account.$userId'; - } - - /// Generate a files channel string. - /// - /// [bucketId] The bucket ID (default: '*') - /// [fileId] The file ID (default: '*') - /// [action] Optional action: 'create', 'update', or 'delete' (default: null) - static String files({ - String bucketId = '*', - String fileId = '*', - String? action, - }) { - String channel = 'buckets.$bucketId.files.$fileId'; - if (action != null) { - channel += '.$action'; - } - return channel; - } - - /// Generate an executions channel string. - /// - /// [functionId] The function ID (default: '*') - /// [executionId] The execution ID (default: '*') - /// [action] Optional action: 'create', 'update', or 'delete' (default: null) - static String executions({ - String functionId = '*', - String executionId = '*', - String? action, - }) { - String channel = 'functions.$functionId.executions.$executionId'; - if (action != null) { - channel += '.$action'; - } - return channel; - } - - /// Generate a teams channel string. - /// - /// [teamId] The team ID (default: '*') - /// [action] Optional action: 'create', 'update', or 'delete' (default: null) - static String teams({ - String teamId = '*', - String? action, - }) { - String channel = 'teams.$teamId'; - if (action != null) { - channel += '.$action'; - } - return channel; - } - - /// Generate a memberships channel string. - /// - /// [membershipId] The membership ID (default: '*') - /// [action] Optional action: 'create', 'update', or 'delete' (default: null) - static String memberships({ - String membershipId = '*', - String? action, - }) { - String channel = 'memberships.$membershipId'; - if (action != null) { - channel += '.$action'; - } - return channel; - } -} - diff --git a/templates/flutter/lib/channel.dart.twig b/templates/flutter/lib/channel.dart.twig new file mode 100644 index 000000000..1515f01f3 --- /dev/null +++ b/templates/flutter/lib/channel.dart.twig @@ -0,0 +1,239 @@ +part of '{{ language.params.packageName }}.dart'; + +class ResolvedChannel { + final String _value; + + ResolvedChannel(this._value); + + String toString() { + return _value; + } +} + +abstract class ActionChannel { + final String base; + + ActionChannel(this.base); + + ResolvedChannel create() { + return ResolvedChannel('$base.create'); + } + + ResolvedChannel update() { + return ResolvedChannel('$base.update'); + } + + ResolvedChannel delete() { + return ResolvedChannel('$base.delete'); + } + + @override + String toString() { + return base; + } +} + +/** + * ---------------------------- + * Database → Collection → Document + * ---------------------------- + */ +class DocumentChannel extends ActionChannel { + DocumentChannel(String base) : super(base); +} + +class CollectionChannel { + final String base; + final String databaseId; + final String collectionId; + + CollectionChannel(this.databaseId, this.collectionId) + : base = 'databases.$databaseId.collections.$collectionId'; + + DocumentChannel document([String documentId = '*']) { + return DocumentChannel('$base.documents.$documentId'); + } + + @override + String toString() { + return base; + } +} + +class DatabaseChannel { + final String base; + final String databaseId; + + DatabaseChannel(this.databaseId) : base = 'databases.$databaseId'; + + CollectionChannel collection([String collectionId = '*']) { + return CollectionChannel(databaseId, collectionId); + } + + @override + String toString() { + return base; + } +} + +/** + * ---------------------------- + * TablesDB → Table → Row + * ---------------------------- + */ +class RowChannel extends ActionChannel { + RowChannel(String base) : super(base); +} + +class TableChannel { + final String base; + final String databaseId; + final String tableId; + + TableChannel(this.databaseId, this.tableId) + : base = 'tablesdb.$databaseId.tables.$tableId'; + + RowChannel row([String rowId = '*']) { + return RowChannel('$base.rows.$rowId'); + } + + @override + String toString() { + return base; + } +} + +class TablesDBChannel { + final String base; + final String databaseId; + + TablesDBChannel(this.databaseId) : base = 'tablesdb.$databaseId'; + + TableChannel table([String tableId = '*']) { + return TableChannel(databaseId, tableId); + } + + @override + String toString() { + return base; + } +} + +/** + * ---------------------------- + * Buckets → File + * ---------------------------- + */ +class FileChannel extends ActionChannel { + FileChannel(String base) : super(base); +} + +class BucketChannel { + final String base; + final String bucketId; + + BucketChannel(this.bucketId) : base = 'buckets.$bucketId'; + + FileChannel file([String fileId = '*']) { + return FileChannel('$base.files.$fileId'); + } + + @override + String toString() { + return base; + } +} + +/** + * ---------------------------- + * Functions → Execution + * ---------------------------- + */ +class ExecutionChannel extends ActionChannel { + ExecutionChannel(String base) : super(base); +} + +class FunctionChannel { + final String base; + final String functionId; + + FunctionChannel(this.functionId) : base = 'functions.$functionId'; + + ExecutionChannel execution([String executionId = '*']) { + return ExecutionChannel('$base.executions.$executionId'); + } + + @override + String toString() { + return base; + } +} + +class TeamChannel extends ActionChannel { + TeamChannel([String teamId = '*']) : super('teams.$teamId'); +} + +class MembershipChannel extends ActionChannel { + MembershipChannel([String membershipId = '*']) + : super('memberships.$membershipId'); +} + +typedef ChannelValue = Object; + +class Channel { + /// Generate a database channel builder. + /// + /// [databaseId] The database ID (default: "*") + /// Returns [DatabaseChannel] + static DatabaseChannel database([String databaseId = '*']) { + return DatabaseChannel(databaseId); + } + + /// Generate a tables database channel builder. + /// + /// [databaseId] The database ID (default: "*") + /// Returns [TablesDBChannel] + static TablesDBChannel tablesdb([String databaseId = '*']) { + return TablesDBChannel(databaseId); + } + + /// Generate a buckets channel builder. + /// + /// [bucketId] The bucket ID (default: "*") + /// Returns [BucketChannel] + static BucketChannel buckets([String bucketId = '*']) { + return BucketChannel(bucketId); + } + + /// Generate a functions channel builder. + /// + /// [functionId] The function ID (default: "*") + /// Returns [FunctionChannel] + static FunctionChannel functions([String functionId = '*']) { + return FunctionChannel(functionId); + } + + /// Generate a teams channel builder. + /// + /// [teamId] The team ID (default: "*") + /// Returns [TeamChannel] + static TeamChannel teams([String teamId = '*']) { + return TeamChannel(teamId); + } + + /// Generate a memberships channel builder. + /// + /// [membershipId] The membership ID (default: "*") + /// Returns [MembershipChannel] + static MembershipChannel memberships([String membershipId = '*']) { + return MembershipChannel(membershipId); + } + + /// Generate an account channel string. + /// + /// [userId] The user ID (default: "*") + /// Returns The channel string + static String account([String userId = '*']) { + return 'account.$userId'; + } +} \ No newline at end of file diff --git a/templates/flutter/lib/src/realtime.dart.twig b/templates/flutter/lib/src/realtime.dart.twig index e02d89a56..5cf072a16 100644 --- a/templates/flutter/lib/src/realtime.dart.twig +++ b/templates/flutter/lib/src/realtime.dart.twig @@ -42,7 +42,15 @@ abstract class Realtime extends Service { /// subscription.close(); /// ``` /// - RealtimeSubscription subscribe(List channels); + /// You can also use Channel builders: + /// ```dart + /// final subscription = realtime.subscribe([ + /// Channel.database('db').collection('col').document('doc').create(), + /// Channel.buckets('bucket').file('file').update(), + /// 'account.*' + /// ]); + /// ``` + RealtimeSubscription subscribe(List channels); /// The [close code](https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5) set when the WebSocket connection is closed. /// diff --git a/templates/flutter/lib/src/realtime_base.dart.twig b/templates/flutter/lib/src/realtime_base.dart.twig index f9fe5e44b..479311074 100644 --- a/templates/flutter/lib/src/realtime_base.dart.twig +++ b/templates/flutter/lib/src/realtime_base.dart.twig @@ -3,5 +3,5 @@ import 'realtime.dart'; abstract class RealtimeBase implements Realtime { @override - RealtimeSubscription subscribe(List channels); + RealtimeSubscription subscribe(List channels); } diff --git a/templates/flutter/lib/src/realtime_browser.dart.twig b/templates/flutter/lib/src/realtime_browser.dart.twig index aa6a3ad14..5d0889066 100644 --- a/templates/flutter/lib/src/realtime_browser.dart.twig +++ b/templates/flutter/lib/src/realtime_browser.dart.twig @@ -35,7 +35,7 @@ class RealtimeBrowser extends RealtimeBase with RealtimeMixin { } @override - RealtimeSubscription subscribe(List channels) { + RealtimeSubscription subscribe(List channels) { return subscribeTo(channels); } } diff --git a/templates/flutter/lib/src/realtime_io.dart.twig b/templates/flutter/lib/src/realtime_io.dart.twig index 27539b251..dd881725b 100644 --- a/templates/flutter/lib/src/realtime_io.dart.twig +++ b/templates/flutter/lib/src/realtime_io.dart.twig @@ -43,7 +43,7 @@ class RealtimeIO extends RealtimeBase with RealtimeMixin { /// Use this method to subscribe to a channels and listen to /// realtime events on those channels @override - RealtimeSubscription subscribe(List channels) { + RealtimeSubscription subscribe(List channels) { return subscribeTo(channels); } diff --git a/templates/flutter/lib/src/realtime_mixin.dart.twig b/templates/flutter/lib/src/realtime_mixin.dart.twig index 96e30699b..e34bc1a9c 100644 --- a/templates/flutter/lib/src/realtime_mixin.dart.twig +++ b/templates/flutter/lib/src/realtime_mixin.dart.twig @@ -168,18 +168,27 @@ mixin RealtimeMixin { ); } - RealtimeSubscription subscribeTo(List channels) { + /// Convert channel value to string + String _channelToString(Object channel) { + if (channel is String) { + return channel; + } + return channel.toString(); + } + + RealtimeSubscription subscribeTo(List channels) { StreamController controller = StreamController.broadcast(); - _channels.addAll(channels); + final channelStrings = channels.map((ch) => _channelToString(ch)).toList().cast(); + _channels.addAll(channelStrings); Future.delayed(Duration.zero, () => _createSocket()); int id = DateTime.now().microsecondsSinceEpoch; RealtimeSubscription subscription = RealtimeSubscription( controller: controller, - channels: channels, + channels: channelStrings, close: () async { _subscriptions.remove(id); controller.close(); - _cleanup(channels); + _cleanup(channelStrings); if (_channels.isNotEmpty) { await Future.delayed(Duration.zero, () => _createSocket()); diff --git a/templates/web/src/channel.ts.twig b/templates/web/src/channel.ts.twig index fe2cada9e..ac14ebdf8 100644 --- a/templates/web/src/channel.ts.twig +++ b/templates/web/src/channel.ts.twig @@ -1,111 +1,266 @@ + + +export class ResolvedChannel { + private value: string; + + constructor(value: string) { + this.value = value; + } + + toString(): string { + return this.value; + } +} + +abstract class ActionChannel { + protected base: string; + + constructor(base: string) { + this.base = base; + } + + create(): ResolvedChannel { + return new ResolvedChannel(`${this.base}.create`); + } + + update(): ResolvedChannel { + return new ResolvedChannel(`${this.base}.update`); + } + + delete(): ResolvedChannel { + return new ResolvedChannel(`${this.base}.delete`); + } + + toString(): string { + return this.base; + } +} + +/** + * ---------------------------- + * Database → Collection → Document + * ---------------------------- + */ +class DocumentChannel extends ActionChannel {} + +class CollectionChannel { + private base: string; + + constructor( + private databaseId: string, + private collectionId: string + ) { + this.base = `databases.${databaseId}.collections.${collectionId}`; + } + + document(documentId: string = '*'): DocumentChannel { + return new DocumentChannel( + `${this.base}.documents.${documentId}` + ); + } + + toString(): string { + return this.base; + } +} + +class DatabaseChannel { + private base: string; + + constructor(private databaseId: string) { + this.base = `databases.${databaseId}`; + } + + collection(collectionId: string = '*'): CollectionChannel { + return new CollectionChannel(this.databaseId, collectionId); + } + + toString(): string { + return this.base; + } +} + +/** + * ---------------------------- + * TablesDB → Table → Row + * ---------------------------- + */ +class RowChannel extends ActionChannel {} + +class TableChannel { + private base: string; + + constructor( + private databaseId: string, + private tableId: string + ) { + this.base = `tablesdb.${databaseId}.tables.${tableId}`; + } + + row(rowId: string = '*'): RowChannel { + return new RowChannel( + `${this.base}.rows.${rowId}` + ); + } + + toString(): string { + return this.base; + } +} + +class TablesDBChannel { + private base: string; + + constructor(private databaseId: string) { + this.base = `tablesdb.${databaseId}`; + } + + table(tableId: string = '*'): TableChannel { + return new TableChannel(this.databaseId, tableId); + } + + toString(): string { + return this.base; + } +} + +/** + * ---------------------------- + * Buckets → File + * ---------------------------- + */ +class FileChannel extends ActionChannel {} + +class BucketChannel { + private base: string; + + constructor(private bucketId: string) { + this.base = `buckets.${bucketId}`; + } + + file(fileId: string = '*'): FileChannel { + return new FileChannel( + `${this.base}.files.${fileId}` + ); + } + + toString(): string { + return this.base; + } +} + /** - * Helper class to generate channel strings for realtime subscriptions. + * ---------------------------- + * Functions → Execution + * ---------------------------- */ +class ExecutionChannel extends ActionChannel {} + +class FunctionChannel { + private base: string; + + constructor(private functionId: string) { + this.base = `functions.${functionId}`; + } + + execution(executionId: string = '*'): ExecutionChannel { + return new ExecutionChannel( + `${this.base}.executions.${executionId}` + ); + } + + toString(): string { + return this.base; + } +} + +class TeamChannel extends ActionChannel { + constructor(teamId: string = '*') { + super(`teams.${teamId}`); + } +} + +class MembershipChannel extends ActionChannel { + constructor(membershipId: string = '*') { + super(`memberships.${membershipId}`); + } +} + +/** + * ---------------------------- + * Channel Type (for type checking) + * ---------------------------- + */ +export type ChannelValue = string | ResolvedChannel | DatabaseChannel | CollectionChannel | DocumentChannel | TablesDBChannel | TableChannel | RowChannel | BucketChannel | FileChannel | FunctionChannel | ExecutionChannel | TeamChannel | MembershipChannel; + export class Channel { /** - * Generate a database channel string. + * Generate a database channel builder. * * @param {string} databaseId - * @param {string} collectionId - * @param {string} documentId - * @param {'create'|'update'|'delete'|null} action - * @returns {string} + * @returns {DatabaseChannel} */ - static database(databaseId: string = '*', collectionId: string = '*', documentId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { - let channel = `databases.${databaseId}.collections.${collectionId}.documents.${documentId}`; - if (action) { - channel += `.${action}`; - } - return channel; + static database(databaseId: string = '*'): DatabaseChannel { + return new DatabaseChannel(databaseId); } /** - * Generate a tables database channel string. + * Generate a tables database channel builder. * * @param {string} databaseId - * @param {string} tableId - * @param {string} rowId - * @param {'create'|'update'|'delete'|null} action - * @returns {string} + * @returns {TablesDBChannel} */ - static tablesdb(databaseId: string = '*', tableId: string = '*', rowId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { - let channel = `tablesdb.${databaseId}.tables.${tableId}.rows.${rowId}`; - if (action) { - channel += `.${action}`; - } - return channel; + static tablesdb(databaseId: string = '*'): TablesDBChannel { + return new TablesDBChannel(databaseId); } /** - * Generate an account channel string. + * Generate a buckets channel builder. * - * @param {string} userId - * @returns {string} + * @param {string} bucketId + * @returns {BucketChannel} */ - static account(userId: string = '*'): string { - return `account.${userId}`; + static buckets(bucketId: string = '*'): BucketChannel { + return new BucketChannel(bucketId); } /** - * Generate a files channel string. + * Generate a functions channel builder. * - * @param {string} bucketId - * @param {string} fileId - * @param {'create'|'update'|'delete'|null} action - * @returns {string} + * @param {string} functionId + * @returns {FunctionChannel} */ - static files(bucketId: string = '*', fileId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { - let channel = `buckets.${bucketId}.files.${fileId}`; - if (action) { - channel += `.${action}`; - } - return channel; + static functions(functionId: string = '*'): FunctionChannel { + return new FunctionChannel(functionId); } /** - * Generate an executions channel string. + * Generate a teams channel builder. * - * @param {string} functionId - * @param {string} executionId - * @param {'create'|'update'|'delete'|null} action - * @returns {string} + * @param {string} teamId + * @returns {TeamChannel} */ - static executions(functionId: string = '*', executionId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { - let channel = `functions.${functionId}.executions.${executionId}`; - if (action) { - channel += `.${action}`; - } - return channel; + static teams(teamId: string = '*'): TeamChannel { + return new TeamChannel(teamId); } /** - * Generate a teams channel string. + * Generate a memberships channel builder. * - * @param {string} teamId - * @param {'create'|'update'|'delete'|null} action - * @returns {string} + * @param {string} membershipId + * @returns {MembershipChannel} */ - static teams(teamId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { - let channel = `teams.${teamId}`; - if (action) { - channel += `.${action}`; - } - return channel; + static memberships(membershipId: string = '*'): MembershipChannel { + return new MembershipChannel(membershipId); } /** - * Generate a memberships channel string. + * Generate an account channel string. * - * @param {string} membershipId - * @param {'create'|'update'|'delete'|null} action + * @param {string} userId * @returns {string} */ - static memberships(membershipId: string = '*', action: 'create' | 'update' | 'delete' | null = null): string { - let channel = `memberships.${membershipId}`; - if (action) { - channel += `.${action}`; - } - return channel; + static account(userId: string = '*'): string { + return `account.${userId}`; } } - diff --git a/templates/web/src/client.ts.twig b/templates/web/src/client.ts.twig index 358e30bfb..639507d29 100644 --- a/templates/web/src/client.ts.twig +++ b/templates/web/src/client.ts.twig @@ -1,4 +1,5 @@ import { Models } from './models'; +import { ChannelValue } from './channel'; /** * Payload type representing a key-value pair with string keys and any values. @@ -552,8 +553,8 @@ class Client { * @deprecated Use the Realtime service instead. * @see Realtime * - * @param {string|string[]} channels - * Channel to subscribe - pass a single channel as a string or multiple with an array of strings. + * @param {string|string[]|ChannelValue|ChannelValue[]} channels + * Channel to subscribe - pass a single channel as a string or Channel builder instance, or multiple with an array. * * Possible channels are: * - account @@ -571,16 +572,34 @@ class Client { * - teams.[ID] * - memberships * - memberships.[ID] + * + * You can also use Channel builders: + * - Channel.database('db').collection('col').document('doc').create() + * - Channel.buckets('bucket').file('file').update() + * - Channel.functions('func').execution('exec').delete() + * - Channel.teams('team').create() + * - Channel.memberships('membership').update() * @param {(payload: RealtimeMessage) => void} callback Is called on every realtime update. * @returns {() => void} Unsubscribes from events. */ - subscribe(channels: string | string[], callback: (payload: RealtimeResponseEvent) => void): () => void { - let channelArray = typeof channels === 'string' ? [channels] : channels; - channelArray.forEach(channel => this.realtime.channels.add(channel)); + subscribe(channels: string | string[] | ChannelValue | ChannelValue[], callback: (payload: RealtimeResponseEvent) => void): () => void { + const channelArray = Array.isArray(channels) ? channels : [channels]; + // Convert Channel instances to strings + const channelStrings = channelArray.map(ch => { + if (typeof ch === 'string') { + return ch; + } + // Channel builder instances have toString() method + if (ch && typeof ch.toString === 'function') { + return ch.toString(); + } + return String(ch); + }); + channelStrings.forEach(channel => this.realtime.channels.add(channel)); const counter = this.realtime.subscriptionsCounter++; this.realtime.subscriptions.set(counter, { - channels: channelArray, + channels: channelStrings, callback }); @@ -588,7 +607,7 @@ class Client { return () => { this.realtime.subscriptions.delete(counter); - this.realtime.cleanUp(channelArray); + this.realtime.cleanUp(channelStrings); this.realtime.connect(); } } diff --git a/templates/web/src/services/realtime.ts.twig b/templates/web/src/services/realtime.ts.twig index d30e716ea..d03e93903 100644 --- a/templates/web/src/services/realtime.ts.twig +++ b/templates/web/src/services/realtime.ts.twig @@ -1,4 +1,5 @@ import { {{ spec.title | caseUcfirst}}Exception, Client } from '../client'; +import { ChannelValue } from '../channel'; export type RealtimeSubscription = { close: () => Promise; @@ -237,61 +238,83 @@ export class Realtime { return new Promise(resolve => setTimeout(resolve, ms)); } + /** + * Convert a channel value to a string + * + * @private + * @param {string | ChannelValue} channel - Channel value (string or Channel builder instance) + * @returns {string} Channel string representation + */ + private channelToString(channel: string | ChannelValue): string { + if (typeof channel === 'string') { + return channel; + } + // Channel builder instances have toString() method + if (channel && typeof channel.toString === 'function') { + return channel.toString(); + } + return String(channel); + } + /** * Subscribe to a single channel * - * @param {string} channel - Channel name to subscribe to + * @param {string | ChannelValue} channel - Channel name to subscribe to (string or Channel builder instance) * @param {Function} callback - Callback function to handle events * @returns {Promise} Subscription object with close method */ public async subscribe( - channel: string, + channel: ChannelValue, callback: (event: RealtimeResponseEvent) => void ): Promise; /** * Subscribe to multiple channels * - * @param {string[]} channels - Array of channel names to subscribe to + * @param {(string | ChannelValue)[]} channels - Array of channel names to subscribe to (strings or Channel builder instances) * @param {Function} callback - Callback function to handle events * @returns {Promise} Subscription object with close method */ public async subscribe( - channels: string[], + channels: (string | ChannelValue)[], callback: (event: RealtimeResponseEvent) => void ): Promise; /** * Subscribe to a single channel with typed payload * - * @param {string} channel - Channel name to subscribe to + * @param {string | ChannelValue} channel - Channel name to subscribe to (string or Channel builder instance) * @param {Function} callback - Callback function to handle events with typed payload * @returns {Promise} Subscription object with close method */ public async subscribe( - channel: string, + channel: ChannelValue, callback: (event: RealtimeResponseEvent) => void ): Promise; /** * Subscribe to multiple channels with typed payload * - * @param {string[]} channels - Array of channel names to subscribe to + * @param {(string | ChannelValue)[]} channels - Array of channel names to subscribe to (strings or Channel builder instances) * @param {Function} callback - Callback function to handle events with typed payload * @returns {Promise} Subscription object with close method */ public async subscribe( - channels: string[], + channels: (string | ChannelValue)[], callback: (event: RealtimeResponseEvent) => void ): Promise; public async subscribe( - channelsOrChannel: string | string[], + channelsOrChannel: ChannelValue | (string | ChannelValue)[], callback: (event: RealtimeResponseEvent) => void ): Promise { - const channels = Array.isArray(channelsOrChannel) - ? new Set(channelsOrChannel) - : new Set([channelsOrChannel]); + const channelArray = Array.isArray(channelsOrChannel) + ? channelsOrChannel + : [channelsOrChannel]; + + // Convert all channels to strings + const channelStrings = channelArray.map(ch => this.channelToString(ch)); + const channels = new Set(channelStrings); this.subscriptionsCounter++; const count = this.subscriptionsCounter; diff --git a/tests/languages/android/Tests.kt b/tests/languages/android/Tests.kt index 579bc0019..58013f647 100644 --- a/tests/languages/android/Tests.kt +++ b/tests/languages/android/Tests.kt @@ -271,26 +271,26 @@ class ServiceTest { writeToFile(ID.custom("custom_id")) // Channel helper tests - writeToFile(Channel.database()) - writeToFile(Channel.database("db1", "col1", "doc1")) - writeToFile(Channel.database("db1", "col1", "doc1", "create")) - writeToFile(Channel.tablesdb()) - writeToFile(Channel.tablesdb("db1", "table1", "row1")) - writeToFile(Channel.tablesdb("db1", "table1", "row1", "update")) + writeToFile(Channel.database().collection().document().toString()) + writeToFile(Channel.database("db1").collection("col1").document("doc1").toString()) + writeToFile(Channel.database("db1").collection("col1").document("doc1").create().toString()) + writeToFile(Channel.tablesdb().table().row().toString()) + writeToFile(Channel.tablesdb("db1").table("table1").row("row1").toString()) + writeToFile(Channel.tablesdb("db1").table("table1").row("row1").update().toString()) writeToFile(Channel.account()) writeToFile(Channel.account("user123")) - writeToFile(Channel.files()) - writeToFile(Channel.files("bucket1", "file1")) - writeToFile(Channel.files("bucket1", "file1", "delete")) - writeToFile(Channel.executions()) - writeToFile(Channel.executions("func1", "exec1")) - writeToFile(Channel.executions("func1", "exec1", "create")) - writeToFile(Channel.teams()) - writeToFile(Channel.teams("team1")) - writeToFile(Channel.teams("team1", "create")) - writeToFile(Channel.memberships()) - writeToFile(Channel.memberships("membership1")) - writeToFile(Channel.memberships("membership1", "update")) + writeToFile(Channel.buckets().file().toString()) + writeToFile(Channel.buckets("bucket1").file("file1").toString()) + writeToFile(Channel.buckets("bucket1").file("file1").delete().toString()) + writeToFile(Channel.functions().execution().toString()) + writeToFile(Channel.functions("func1").execution("exec1").toString()) + writeToFile(Channel.functions("func1").execution("exec1").create().toString()) + writeToFile(Channel.teams().toString()) + writeToFile(Channel.teams("team1").toString()) + writeToFile(Channel.teams("team1").create().toString()) + writeToFile(Channel.memberships().toString()) + writeToFile(Channel.memberships("membership1").toString()) + writeToFile(Channel.memberships("membership1").update().toString()) // Operator helper tests writeToFile(Operator.increment(1)) diff --git a/tests/languages/apple/Tests.swift b/tests/languages/apple/Tests.swift index 8917ce1b2..50bde946c 100644 --- a/tests/languages/apple/Tests.swift +++ b/tests/languages/apple/Tests.swift @@ -249,26 +249,26 @@ class Tests: XCTestCase { print(ID.custom("custom_id")) // Channel helper tests - print(Channel.database()) - print(Channel.database(databaseId: "db1", collectionId: "col1", documentId: "doc1")) - print(Channel.database(databaseId: "db1", collectionId: "col1", documentId: "doc1", action: "create")) - print(Channel.tablesdb()) - print(Channel.tablesdb(databaseId: "db1", tableId: "table1", rowId: "row1")) - print(Channel.tablesdb(databaseId: "db1", tableId: "table1", rowId: "row1", action: "update")) + print(Channel.database().collection().document().toString()) + print(Channel.database("db1").collection("col1").document("doc1").toString()) + print(Channel.database("db1").collection("col1").document("doc1").create().toString()) + print(Channel.tablesdb().table().row().toString()) + print(Channel.tablesdb("db1").table("table1").row("row1").toString()) + print(Channel.tablesdb("db1").table("table1").row("row1").update().toString()) print(Channel.account()) - print(Channel.account(userId: "user123")) - print(Channel.files()) - print(Channel.files(bucketId: "bucket1", fileId: "file1")) - print(Channel.files(bucketId: "bucket1", fileId: "file1", action: "delete")) - print(Channel.executions()) - print(Channel.executions(functionId: "func1", executionId: "exec1")) - print(Channel.executions(functionId: "func1", executionId: "exec1", action: "create")) - print(Channel.teams()) - print(Channel.teams(teamId: "team1")) - print(Channel.teams(teamId: "team1", action: "create")) - print(Channel.memberships()) - print(Channel.memberships(membershipId: "membership1")) - print(Channel.memberships(membershipId: "membership1", action: "update")) + print(Channel.account("user123")) + print(Channel.buckets().file().toString()) + print(Channel.buckets("bucket1").file("file1").toString()) + print(Channel.buckets("bucket1").file("file1").delete().toString()) + print(Channel.functions().execution().toString()) + print(Channel.functions("func1").execution("exec1").toString()) + print(Channel.functions("func1").execution("exec1").create().toString()) + print(Channel.teams().toString()) + print(Channel.teams("team1").toString()) + print(Channel.teams("team1").create().toString()) + print(Channel.memberships().toString()) + print(Channel.memberships("membership1").toString()) + print(Channel.memberships("membership1").update().toString()) // Operator helper tests print(Operator.increment(1)) diff --git a/tests/languages/flutter/tests.dart b/tests/languages/flutter/tests.dart index c6ab56563..7647041c6 100644 --- a/tests/languages/flutter/tests.dart +++ b/tests/languages/flutter/tests.dart @@ -248,26 +248,26 @@ void main() async { print(ID.custom('custom_id')); // Channel helper tests - print(Channel.database()); - print(Channel.database(databaseId: 'db1', collectionId: 'col1', documentId: 'doc1')); - print(Channel.database(databaseId: 'db1', collectionId: 'col1', documentId: 'doc1', action: 'create')); - print(Channel.tablesdb()); - print(Channel.tablesdb(databaseId: 'db1', tableId: 'table1', rowId: 'row1')); - print(Channel.tablesdb(databaseId: 'db1', tableId: 'table1', rowId: 'row1', action: 'update')); + print(Channel.database().collection().document().toString()); + print(Channel.database('db1').collection('col1').document('doc1').toString()); + print(Channel.database('db1').collection('col1').document('doc1').create().toString()); + print(Channel.tablesdb().table().row().toString()); + print(Channel.tablesdb('db1').table('table1').row('row1').toString()); + print(Channel.tablesdb('db1').table('table1').row('row1').update().toString()); print(Channel.account()); - print(Channel.account(userId: 'user123')); - print(Channel.files()); - print(Channel.files(bucketId: 'bucket1', fileId: 'file1')); - print(Channel.files(bucketId: 'bucket1', fileId: 'file1', action: 'delete')); - print(Channel.executions()); - print(Channel.executions(functionId: 'func1', executionId: 'exec1')); - print(Channel.executions(functionId: 'func1', executionId: 'exec1', action: 'create')); - print(Channel.teams()); - print(Channel.teams(teamId: 'team1')); - print(Channel.teams(teamId: 'team1', action: 'create')); - print(Channel.memberships()); - print(Channel.memberships(membershipId: 'membership1')); - print(Channel.memberships(membershipId: 'membership1', action: 'update')); + print(Channel.account('user123')); + print(Channel.buckets().file().toString()); + print(Channel.buckets('bucket1').file('file1').toString()); + print(Channel.buckets('bucket1').file('file1').delete().toString()); + print(Channel.functions().execution().toString()); + print(Channel.functions('func1').execution('exec1').toString()); + print(Channel.functions('func1').execution('exec1').create().toString()); + print(Channel.teams().toString()); + print(Channel.teams('team1').toString()); + print(Channel.teams('team1').create().toString()); + print(Channel.memberships().toString()); + print(Channel.memberships('membership1').toString()); + print(Channel.memberships('membership1').update().toString()); // Operator helper tests print(Operator.increment(1)); diff --git a/tests/languages/web/index.html b/tests/languages/web/index.html index f85cbad04..e8b4d48fe 100644 --- a/tests/languages/web/index.html +++ b/tests/languages/web/index.html @@ -320,26 +320,26 @@ console.log(ID.custom('custom_id')); // Channel helper tests - console.log(Channel.database()); - console.log(Channel.database('db1', 'col1', 'doc1')); - console.log(Channel.database('db1', 'col1', 'doc1', 'create')); - console.log(Channel.tablesdb()); - console.log(Channel.tablesdb('db1', 'table1', 'row1')); - console.log(Channel.tablesdb('db1', 'table1', 'row1', 'update')); + console.log(Channel.database().collection().document().toString()); + console.log(Channel.database('db1').collection('col1').document('doc1').toString()); + console.log(Channel.database('db1').collection('col1').document('doc1').create().toString()); + console.log(Channel.tablesdb().table().row().toString()); + console.log(Channel.tablesdb('db1').table('table1').row('row1').toString()); + console.log(Channel.tablesdb('db1').table('table1').row('row1').update().toString()); console.log(Channel.account()); console.log(Channel.account('user123')); - console.log(Channel.files()); - console.log(Channel.files('bucket1', 'file1')); - console.log(Channel.files('bucket1', 'file1', 'delete')); - console.log(Channel.executions()); - console.log(Channel.executions('func1', 'exec1')); - console.log(Channel.executions('func1', 'exec1', 'create')); - console.log(Channel.teams()); - console.log(Channel.teams('team1')); - console.log(Channel.teams('team1', 'create')); - console.log(Channel.memberships()); - console.log(Channel.memberships('membership1')); - console.log(Channel.memberships('membership1', 'update')); + console.log(Channel.buckets().file().toString()); + console.log(Channel.buckets('bucket1').file('file1').toString()); + console.log(Channel.buckets('bucket1').file('file1').delete().toString()); + console.log(Channel.functions().execution().toString()); + console.log(Channel.functions('func1').execution('exec1').toString()); + console.log(Channel.functions('func1').execution('exec1').create().toString()); + console.log(Channel.teams().toString()); + console.log(Channel.teams('team1').toString()); + console.log(Channel.teams('team1').create().toString()); + console.log(Channel.memberships().toString()); + console.log(Channel.memberships('membership1').toString()); + console.log(Channel.memberships('membership1').update().toString()); // Operator helper tests console.log(Operator.increment(1)); diff --git a/tests/languages/web/node.js b/tests/languages/web/node.js index 45bf89fee..8e6ebd824 100644 --- a/tests/languages/web/node.js +++ b/tests/languages/web/node.js @@ -251,26 +251,26 @@ async function start() { console.log(ID.custom('custom_id')); // Channel helper tests - console.log(Channel.database()); - console.log(Channel.database('db1', 'col1', 'doc1')); - console.log(Channel.database('db1', 'col1', 'doc1', 'create')); - console.log(Channel.tablesdb()); - console.log(Channel.tablesdb('db1', 'table1', 'row1')); - console.log(Channel.tablesdb('db1', 'table1', 'row1', 'update')); + console.log(Channel.database().collection().document().toString()); + console.log(Channel.database('db1').collection('col1').document('doc1').toString()); + console.log(Channel.database('db1').collection('col1').document('doc1').create().toString()); + console.log(Channel.tablesdb().table().row().toString()); + console.log(Channel.tablesdb('db1').table('table1').row('row1').toString()); + console.log(Channel.tablesdb('db1').table('table1').row('row1').update().toString()); console.log(Channel.account()); console.log(Channel.account('user123')); - console.log(Channel.files()); - console.log(Channel.files('bucket1', 'file1')); - console.log(Channel.files('bucket1', 'file1', 'delete')); - console.log(Channel.executions()); - console.log(Channel.executions('func1', 'exec1')); - console.log(Channel.executions('func1', 'exec1', 'create')); - console.log(Channel.teams()); - console.log(Channel.teams('team1')); - console.log(Channel.teams('team1', 'create')); - console.log(Channel.memberships()); - console.log(Channel.memberships('membership1')); - console.log(Channel.memberships('membership1', 'update')); + console.log(Channel.buckets().file().toString()); + console.log(Channel.buckets('bucket1').file('file1').toString()); + console.log(Channel.buckets('bucket1').file('file1').delete().toString()); + console.log(Channel.functions().execution().toString()); + console.log(Channel.functions('func1').execution('exec1').toString()); + console.log(Channel.functions('func1').execution('exec1').create().toString()); + console.log(Channel.teams().toString()); + console.log(Channel.teams('team1').toString()); + console.log(Channel.teams('team1').create().toString()); + console.log(Channel.memberships().toString()); + console.log(Channel.memberships('membership1').toString()); + console.log(Channel.memberships('membership1').update().toString()); // Operator helper tests console.log(Operator.increment(1)); From 3e2402d5830819767d207f009689984789de638e Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Fri, 19 Dec 2025 18:45:55 +0530 Subject: [PATCH 6/6] updated flutter config --- src/SDK/Language/Dart.php | 10 -- src/SDK/Language/Flutter.php | 2 +- templates/dart/lib/package.dart.twig | 1 - .../flutter/test/src/channel_test.dart.twig | 108 ++++++++++++++++++ 4 files changed, 109 insertions(+), 12 deletions(-) create mode 100644 templates/flutter/test/src/channel_test.dart.twig diff --git a/src/SDK/Language/Dart.php b/src/SDK/Language/Dart.php index be3067cda..6ad8efbaa 100644 --- a/src/SDK/Language/Dart.php +++ b/src/SDK/Language/Dart.php @@ -354,11 +354,6 @@ public function getFiles(): array 'destination' => '/lib/id.dart', 'template' => 'dart/lib/id.dart.twig', ], - [ - 'scope' => 'default', - 'destination' => '/lib/channel.dart', - 'template' => 'dart/lib/channel.dart.twig', - ], [ 'scope' => 'default', 'destination' => '/lib/query.dart', @@ -469,11 +464,6 @@ public function getFiles(): array 'destination' => '/test/role_test.dart', 'template' => 'dart/test/role_test.dart.twig', ], - [ - 'scope' => 'default', - 'destination' => '/test/channel_test.dart', - 'template' => 'dart/test/channel_test.dart.twig', - ], [ 'scope' => 'default', 'destination' => '/test/src/enums_test.dart', diff --git a/src/SDK/Language/Flutter.php b/src/SDK/Language/Flutter.php index 0643939a3..cfb6933cc 100644 --- a/src/SDK/Language/Flutter.php +++ b/src/SDK/Language/Flutter.php @@ -298,7 +298,7 @@ public function getFiles(): array [ 'scope' => 'default', 'destination' => '/test/channel_test.dart', - 'template' => 'dart/test/channel_test.dart.twig', + 'template' => 'flutter/test/src/channel_test.dart.twig', ], [ 'scope' => 'default', diff --git a/templates/dart/lib/package.dart.twig b/templates/dart/lib/package.dart.twig index 43e4a2c76..bca6862a3 100644 --- a/templates/dart/lib/package.dart.twig +++ b/templates/dart/lib/package.dart.twig @@ -27,7 +27,6 @@ part 'query.dart'; part 'permission.dart'; part 'role.dart'; part 'id.dart'; -part 'channel.dart'; part 'operator.dart'; {% for service in spec.services %} part 'services/{{service.name | caseSnake}}.dart'; diff --git a/templates/flutter/test/src/channel_test.dart.twig b/templates/flutter/test/src/channel_test.dart.twig new file mode 100644 index 000000000..f531049ec --- /dev/null +++ b/templates/flutter/test/src/channel_test.dart.twig @@ -0,0 +1,108 @@ +import 'package:{{ language.params.packageName }}/{{ language.params.packageName }}.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('database()', () { + test('returns database channel with defaults', () { + expect(Channel.database().collection().document().toString(), 'databases.*.collections.*.documents.*'); + }); + + test('returns database channel with specific IDs', () { + expect(Channel.database('db1').collection('col1').document('doc1').toString(), + 'databases.db1.collections.col1.documents.doc1'); + }); + + test('returns database channel with action', () { + expect(Channel.database('db1').collection('col1').document('doc1').create().toString(), + 'databases.db1.collections.col1.documents.doc1.create'); + }); + }); + + group('tablesdb()', () { + test('returns tablesdb channel with defaults', () { + expect(Channel.tablesdb().table().row().toString(), 'tablesdb.*.tables.*.rows.*'); + }); + + test('returns tablesdb channel with specific IDs', () { + expect(Channel.tablesdb('db1').table('table1').row('row1').toString(), + 'tablesdb.db1.tables.table1.rows.row1'); + }); + + test('returns tablesdb channel with action', () { + expect(Channel.tablesdb('db1').table('table1').row('row1').update().toString(), + 'tablesdb.db1.tables.table1.rows.row1.update'); + }); + }); + + group('account()', () { + test('returns account channel with default', () { + expect(Channel.account(), 'account.*'); + }); + + test('returns account channel with specific user ID', () { + expect(Channel.account('user123'), 'account.user123'); + }); + }); + + group('buckets()', () { + test('returns buckets channel with defaults', () { + expect(Channel.buckets().file().toString(), 'buckets.*.files.*'); + }); + + test('returns buckets channel with specific IDs', () { + expect(Channel.buckets('bucket1').file('file1').toString(), + 'buckets.bucket1.files.file1'); + }); + + test('returns buckets channel with action', () { + expect(Channel.buckets('bucket1').file('file1').delete().toString(), + 'buckets.bucket1.files.file1.delete'); + }); + }); + + group('functions()', () { + test('returns functions channel with defaults', () { + expect(Channel.functions().execution().toString(), 'functions.*.executions.*'); + }); + + test('returns functions channel with specific IDs', () { + expect(Channel.functions('func1').execution('exec1').toString(), + 'functions.func1.executions.exec1'); + }); + + test('returns functions channel with action', () { + expect(Channel.functions('func1').execution('exec1').create().toString(), + 'functions.func1.executions.exec1.create'); + }); + }); + + group('teams()', () { + test('returns teams channel with default', () { + expect(Channel.teams().toString(), 'teams.*'); + }); + + test('returns teams channel with specific team ID', () { + expect(Channel.teams('team1').toString(), 'teams.team1'); + }); + + test('returns teams channel with action', () { + expect(Channel.teams('team1').create().toString(), 'teams.team1.create'); + }); + }); + + group('memberships()', () { + test('returns memberships channel with default', () { + expect(Channel.memberships().toString(), 'memberships.*'); + }); + + test('returns memberships channel with specific membership ID', () { + expect(Channel.memberships('membership1').toString(), 'memberships.membership1'); + }); + + test('returns memberships channel with action', () { + expect(Channel.memberships('membership1').update().toString(), + 'memberships.membership1.update'); + }); + }); +} +